VR 虚拟现实虚拟现实技术
——VRML 篇
虚拟现实技术――VRML篇
一、VRML介绍
1.什么是 VRML?
VRML是“VirtualRealityModelingLanguage”的缩写形式,意思是“虚拟
现实造型语言”。
第一代 Web是以 HTML为核心的二维浏览技术,受 HTML语言的局限性,VRML
之前的网页只能是简单的平面结构,而且实现环境与参与者的动态交互是非常烦
琐的。第二代 Web是以 VRML为核心的三维浏览技术。第二代 Web把 VRML与
HTML、Java、媒体信息流等技术有机地结合起来,形成一种新的三维超媒体
Web。
VRML是 用 来 描 述 三 维 物 体 及 其 行 为 的 , 可 以 构 建 虚 拟 境 界
(VirturalWorld),可以集成文本、图像、音响、MPEG影像等多种媒体类型,还
可以内嵌用 Java、ECMAScript等语言编写的程序代码。
以 VRML为核心构建的虚拟世界中用户如身处真实世界,可以和虚拟物体交
互,人们可以以习惯的自然方式访问各种场所,在虚拟社区中“直接”交谈和交
往。事实上,目前采用 VRML技术取得成功的案例已经很多,例如探路者到达火
星后的信息就是利用 VRML在因特网上即时发布的,网络用户可以以三维方式随
探路者探索火星。
的工作原理
VRML定义了一种把 3D图形和多媒体集成在一起的文件格式。从语法角度看,
VRML文件是显式地定义和组织起来的 3D多媒体对象集合;从语义角度看,VRML
文件描述的是基于时间的交互式 3D多媒体信息的抽象功能行为。VRML文件描述
的基于时间的 3D空间称为虚拟境界(VirtualWorld),简称境界,所包含的图形
对象和听觉对象可通过多种机制动态修改。
VRML文件可以包含对其他标准格式文件的引用。可以把 JPEG、PNG和 MPEG
文件用于对象纹理映射,把 WAV和 MIDI文件用于在境界中播放的声音。另外,
还可以引用包含 Java或 ECMAScript代码的文件,从而实现对象的编程行为。
VRML使用场景图(SceneGraph)数据结构来建立 3D实境,VRML的场景图是
一种代表所有 3D世界静态特征的节点等级:几何关系、质材、纹理、几何转换、
光线、视点以及嵌套结构。几乎所有生产厂商,无论是 CAD、建模、动画、VR,
还是 VRML,他们的结构核心都有场景图。
境界中的对象及其属性用节点(Node)描述,节点按照一定规则构成场景图
(SceneGraph),也就是说,场景图是境界的内部表示。场景图中的第一类节点用
于从视觉和听觉角度表现对象,它们按照层次体系组织起来,反映了境界的空间
结构。另一类节点参与事件产生和路由机制,形成路由图(RouteGraph),确定境
界随时间的推移如何动态变化。
VRML文件的解释、执行和呈现通过浏览器实现,这与利用浏览器显示 HTML
文件的机制完全相同。浏览器把场景图中的形态和声音呈现给用户,这种视听觉
呈现即所谓的虚拟世界(境界)。用户通过浏览器获得的视听觉效果如同从某个特
定方位体验到的,境界中的这种位置和朝向称为取景器(Viewer)。
的应用
VRML在电子商务、教育、工程技术、建筑、娱乐、艺术等领域有广泛的应
用。
例如在教育上,VRML不仅仅是 HTML功能更强的替代品,其潜在意义在于突
破上述基于 WWW的教学模型建立更自然、更真实的虚拟教育环境。在这种环境中
学生可以以浏览探索的方式汲取知识,如进入虚拟太空学习天文知识,利用虚拟
地球学习地理知识,穿过历史长廊与历史人物交流,进入分子世界游历化学殿堂
等等,这些曾经是梦想中的学习方式都可以逐步实现。在这个虚拟教育世界中,
甚至可以有利用 VRML制作的动画人物扮演教师,其面部表情和形体动作利用动
作跟踪系统捕捉下来,这样得到的讲课节目将是三维的。如果把这种方式扩大到
教学双方,则可实现具有实时交互性的虚拟教学——教师控制的虚拟教师和学
生控制的虚拟学生就可以在一个虚拟教室中相互交流。
的工作组及其研究目标
为了推动 VRML技术的发展,VRML协会组织了很多工作组,每个工作组都是
自愿组织、自我约束、并经 VRML协会认可的技术委员会,负责某个与 VRML有关
的专题技术的研究和实现工作。
人性动画工作组(HumanoidAnimationWG)利用 VRML表现人类行为特性。
色彩保真工作组(ColorFidelityWG)确保采用任何平台的观众所看到的效果
都和创作者的原始作品一样,颜色应相当一致。
元形式工作组(MetaFormsWG)针对利用形式文法生成的作品,提出一般性的
方法论和一般性规范,使之能够映射为某种特定形式。首要目标是能够表示"数字
生命格式"(DigitalLife-Forms)结构和增长。
面向对象扩展工作组(Object-OrientedExtensionsWG)探讨和推动对 VRML
进行面向对象扩展的方法。
数据库工作组(DatabaseWG)推进基于 VRML商业应用的创建,利用数据库维
护 VRML内容的持久性、升级能力和安全传输能力。
外部创作接口工作组(ExternalAuthoringInterfaceWG)在 VRML境界和外部
环境之间建立标准接口。
界面组件工作组(WidgetsWG)为开发者和用户提供一套基础性的、可自由使
用的标准用户界面组件集,并提供支持基本组件集和所有 VRML组件的理论框架。
二进制压缩格式工作组(CompressedBinaryFormatWG)探讨并开发 VRML文件
的二进制编码方法,重点是研究为了快速传送目的而尽量缩小文件尺寸,同时为
了快速解码目的而尽量简化文件结构。
通用媒体库工作组(UniversalMediaLibrariesWG)为了提高 VRML境界的真
实感,同时减少网络的下载量,而定义一种由驻留本地的媒体元件(纹理、声音
和 VRML对象)组成的小型跨平台媒体库。同时定义一种统一机制,通过这种机制,
VRML内容创作者可以在自己的境界中使用这些媒体元件。
活动境界工作组(LivingWorldsWG)为多用户(包括多个开发者)应用的产生
和进化定义概念框架,并确定一组界面。
键盘输入工作组(KeyboardInputWG)为了使内容创作者能够在自己的境界中
访问键盘输入,定义一个或多个扩充节点。
一致性工作组(ConformanceWG)为与一致性测试有关的问题提供一个讨论场
所,特别地,本组将辨别 VRML实现发生分歧的地方以及相应的动作序列。
生物圈工作组(BiotaWG)为生命系统(LivingSystem)的研究和学习建立、配
备数字式工具和环境。
分布式交互仿真工作组(DistributedInteractiveSimulationWG)为建立有
多 广 播 能 力 (Multicast-Capable)的 大 规 模 虚 拟 环 境
(Large-ScaleVirtualEnvironments,LSVEs)确立初始网络约定。
VRML脚本工作组(VRMLScriptWG)向 VRML监查组(VRMLReviewBoard,VRB)提
供有关 Java和 JavaScript的问题列表、修改建议和评论。
自然语言处理和动画工作组(NLP&AnimationsWG)为了使用户能使用自然语
言和 VRML动画形象进行交流,从而使交互更自然,增强用户和动画形象之间的信
息流动,研究如何使用“问题/回答”、“命令/响应”式的对话以及基于操作系
统命令和字符控制的自然语言。
VRML-DHTML集成工作组(VRML-DHTMLIntegrationWG)为 VRML和 DHTML在文
档对象模型、组件(Component)接口和绘制等三个层次的紧密集成开发一种概念
模型。
6.研究现状
VRML97发布后,互联网上的 3D图形几乎都使用了 VRML。由于技术的局限性,
如带宽不够,需要下栽插件浏览,文件量大,真实感、交互性需要进一步加强等
原因,最近一二年,许多制作 Web3D图形的软件公司的产品,并没有完全遵循
VRML97标准,而是使用了专用的文件格式和浏览器插件,开发了比较实用的 VR
软件。这些软件有些比 VRML有了进步,在渲染速度、图像质量、造型技术、交
互性以及数据的压缩与优化上,都有胜过 VRML之处。比如, Cult3D、
Viewpoint、GL4Java、Pulse3D、Flatland、Flash、JPEG2000等。
CULT3D、VIEWPOINT、360度环视等技术正被应用。
以 Blaxxun和 ParallelGraphics公司为代表,它们都有各自的 VR浏览器插
件,并各自开发基于 VRML标准的扩展节点功能(X3D),使 3D的效果,交互性
能更加完美;支持 MPEG,Mov、Avi等视频文件,Rm等流媒体文件,Wav、Midi、
Mp3、Aiff等多种音频文件,Flash动画文件,多种材质效果,支持 Nurbs曲线,
粒子效果,雾化效果;支持多人的交互环境,VR眼镜等硬件设备;在娱乐、电
子商务等领域都有成功的应用,并各自为适应 X3D的发展,以 X3D为核心,有
Blaxxun3D等相关产品。在虚拟场景,尤其是大场景的应用方面,以 VRML标准
为核心的技术具有独特的优势。
二、初识 VRML
(一)VRML的文件结构
1.文件头
(VRML97)开头如下:
#
VRML是大小写敏感的,utf8是指一种纯文本编码方式
2.场景图(SceneGraph)
由描述“对象及其属性”的节点组成,节点是 VRML的基本单元,场景图的
第一类节点用于从视觉和听觉角度表现对象,它们是按照层次体系结构组织的;
另一类节点,则参与事件产生和路由机制。
3.原型(Prototype)
用户可以通过原型扩充 VRML的节点类型集。原型的定义可以包含在使用该
原型的文件中,也可以在外部定义。
4.事件路由(Route)
有些 VRML节点能通过产生事件响应环境变化或用户交互。事件一旦产生,
就按时间顺序向路由目标节点发送。目标节点接收后进行相应处理,可改变节点
状态,产生其他事件,或者修改场景图的结构。
利用脚本节点 Script,作者可以 Java或 JavaScript语言自定义任意事件
处理。
(二)VRKL节点和数据类型
虚拟场景由对象构成,对象及其属性用节点(Node)描述,节点是构成 VRML
文件的基本单元。
VRML97定义了 54种基本节点类型(内部节点类型),用户也可以通过原型
机制定义自己的节点类型。
节点由域和事件组成:
1.域(field)
描述了节点的当前状态。其中外露域(exposedField)是域和事件的统一体,
它既作为域描述节点,又隐含着形如“set_域名”的入事件和“域名_changed”
的出事件。
2.事件(event)
分为入事件和出事件,入事件将导致节点状态的改变;出事件向外报告自身
状态的变化。
(三)VRML浏览器
BlaxxunContact3D
CosmoPlayer
Visvape等
(四)编写 VRML境界
1.制作第一个虚拟境界
#
Group{
children[
Shape{
geometryBox{}
}
]
}
将它保存为 文件,则可以用浏览器看到它。
2.定义外观――第二场景
再定义立方体的外观,即改变 Shape节点的 appearance域(外观),
appearance域是一个节点,此节点的 material域定义为一个 Material节点:
则 Shape节点变成了:
Shape{
appearanceAppearance{
materialMaterial{}
}
geometryBox{}
}
修改它的 diffuseColor域(漫射色),应该是{100},3个数字分别表示红色、
绿色和蓝色,取值范围是 0到 1:
materialMaterial{diffuseColor100}
这样,生成了第二个场景文件:
#
Group{
children[
Shape{
appearanceAppearance{
materialMaterial{diffuseColor100}
}
geometryBox{}
]
}
3.定义变换――第三个场景
若想移动这个红色的立方体,可以通过为它外套一个 Transform节点来实现:
Transform{
translation500
children[
Shape{
appearanceAppearance{
materialMaterial{diffuseColor100}
}
geometryBox{}
}
]
}
Transform节点的 translation500表示 x轴向上右移 5个单位(米)
则第三个场景完整代码如下:
#
Group{
children[
Transform{
translation500
children[
Shape{
appearanceAppearance{
materialMaterial{diffuseColor100}
}
geometryBox{}
}
]
}
〕
}
4.复制节点――第四个场景
复制节点,并将各自的几何形状定义为方块、球体和圆椎
Group{
children[
Transform{
translation500
children[
Shape{...geometryBox{}
}
]
}
Transform{
translation000
children[
Shape{...geometrySphere{}
}
]
}
Transform{
translation-500
children[
Shape{...geometryCone{}
}
]
}
]#endofGroupchildren
}
为了以后引用方便,分别给这 3个 Transform节点指定一个名称:
DEFboxTransform{...}
DEFSphereTransform{...}
DEFconeTransform{...}
则第四个场景的完整代码是:
#
Group{
children[
DEFboxTransform{
translation500
children[
Shape{
appearanceAppearance{
materialMaterial{diffuseColor100}
}
geometryBox{}
}
]
}
DEFsphereTransform{
translation000
children[
Shape{
appearanceAppearance{
materialMaterial{diffuseColor010}
}
geometrySphere{}
}
]
}
DEFconeTransform{
translation-500
children[
Shape{
appearanceAppearance{
materialMaterial{diffuseColor001}
}
geometryCone{}
}
]
}
]#endofGroupchildren
}
将此文件保存为 ,用浏览器观看,可从多个方位浏览自己
的作品。
(五)交互能力的加入
1.传感器
是交互能力的基础,共 9 种。在场景中,传感器节点一般是以其他节点的子节点
的身份存在的,它的父节点称为可触发节点,触发条件和时机由传感器节点类型
确定。
接触传感器( TouchSensor)是最常用的传感器,先了解一下开关节点
lightSwitch(组节点),并定义一个接触传感器作为它的子节点:
DEFlightSwitchGroup{
children[
各几何造型节点……
DEFtouchSensorTouchSensor{}
]
}
传感器能引起某种变化,下面看场景变化。
2.视点
当你拖动鼠标或按动箭头键时(按照 VRML术语,称为航行),虚拟境界就会
旋转或缩放,这实际上是在调整你的视点位置或视角。在虚拟场景的重要位置可
以定义视点节点(ViewPoint),它们是境界作者给用户推荐的上佳观赏方位,
在 CosmoPlayer浏览器中,用户就可以通过鼠标右键选择作者推荐的各个视点。
这里我们定义两个视点节点:
DEFview1Viewpoint{#“view1”是编程时引用的名字
position0020
description"View1"#“View1”是浏览器上显示的名字
}
DEFview2Viewpoint{
position5020
description"view2"
}
目的是使用户可以通过触发开关节点来切换视点。视点节点中的坐标表示视点在
场景中的位置,坐标的单位是米,视点的名称将会在浏览器菜单中提示出来供用
户选择。把上述视点说明加入 中(放在 Group节点之前),并
把其中的方块节点修改成可触发节点:
DEFboxTranform{
children[
Shape{....Box...}
DEFtouchBoxTouchSensor{}#定义触发节点
]
}
把修改过的文件另存为“”。
3.事件传递
下面把触发(用鼠标箭头按动方块)和场景变化(视点切换)这两件事情联
系起来,在场景图中,除节点构成的层次体系外,还有一个“事件体系”,事件
体系由相互通讯的节点组成。
能够接收事件的节点都应具有事件入口(eventIn),如果它要接收多种类型
的事件(称为入事件),它就应该具有多个事件入口,也就是说,事件入口象节
点的域一样是有类型的。同样,发送事件的节点应有事件出口(eventOut),事
件出口也是有类型的。例如 ViewPoint节点就有一个事件入口 set_bind,当向此
事件送入一个值“TRUE”(即所谓的入事件)时,该 viewpoint节点成为当前视
点。又如,接触检测器 TouchSensor有一个事件出口 isActive,当受到用户触发
后它就从此出口送出一个“TRUE”(即所谓的出事件),在下一个事件发送之前,
此事件一直保存在事件出口中(作为记录)。
事件出口和事件入口通过路径相连,这就是 VRML文件中除节点以外的另一基本
组成部分:ROUTE语句。ROUTE语句把事件出口和事件入口联系在一起,从而构
成事件体系。在这里,我们是把接触检测器 touchBox的事件出口 isActive连接
到视点节点 view2的事件入口 set_bind:
_bind
现在我们得到的 VRML文件是:
#
DEFview1Viewpoint{#视点
position0020
description"view1"
}
DEFview2Viewpoint{
position5020
description"view2"
}
Group{
children[
DEFboxTransform{
translation500
children[
Shape{
appearanceAppearance{
materialMaterial{
diffuseColor100
}
}
geometryBox{}
}
DEFtouchBoxTouchSensor{}#触感
]
}
DEFsphereTransform{
translation000
children[
Shape{
appearanceAppearance{
materialMaterial{
diffuseColor010
}
}
geometrySphere{}
}
]
}
DEFconeTransform{
translation-500
children[
Shape{
appearanceAppearance{
materialMaterial{
diffuseColor001
}
}
geometryCone{}
}
]
}
]#endofGroupchildren
}
_bind#传递
把这个文件调入浏览器,然后把鼠标指向方块并按下左钮(先别松开!),可以
看到视点已经变为 view2,内部的机制我们已经很清楚:左钮按下时方块节点的
接触检测器被触发,接着接触检测器从事件出口 isActive送出一个事件
“TRUE”,这个事件通过路由进入视点节点 view2的事件入口 set_bind,view2收
到“TRUE”后成为当前视点,所以在我们眼前场景发生了变化。
当松开鼠标左键,又回到原来的视点,称为视点回跳。因为松开鼠标左键后,接
触传感器向 view2发送了“FALSE”事件,,这样 view2的当前地位被解除。若不
想回跳,则要自己来定义。
4.利用脚本编写自定义行为
在 VRML中,利用 Script节点(脚本节点)定义用户自定义行为,所谓定义
即用脚本描述语言(ScriptingLanguage)编写脚本的过程。VRML97支持的脚本
描述语言目前有两种:Java和 EMCAScript(这是 JavaScript标准化后的名称),
关于这两种语言本身,请参考相应参考书,VRML97标准中定义了它们和 VRML的
接口方法。应提请注意的是:VRML是基于节点的语言,所以脚本也是封装在
Script这个特殊节点中的。这里我们不过多讨论脚本描述语言的细节,主要讨
论把脚本集成到 VRML文件中的方法。
上面我们曾把接触检测器 touchBox和视点 view2直接通过路径连接起来,现在
要定义我们指定的行为,就需要在二者之间插入一个脚本节点,也就是让路径绕
个弯:
_bind
其中的脚本节点 touchScript有一个事件人口 touchBoxIsActive和一个事件出
口 bind_View2,前者接收来自接触检测器 touchBox的事件,然后经自己的脚本
处理后,把结果发送给视点节点 view2:
DEFtouchScriptScript{
eventInSFBooltouchBoxIsActive#入口
eventOutSFBoolbindView2#出口
url"javescript:#脚本
functiontouchBoxIsActive(active){#与入口同名的函数被调用
bindView2=TRUE;#返回到出口
}"
}
关于这个 Script节点,请注意一下几点:
(1)它的事件入口 touchBoxIsActive和事件出口 bindView2是自定义的,其它
VRML节点的域和事件都是固定的。
( 2)这里定义的事件入口 touchBoxIsActive(即入事件 )和事件出口
bindView2(即出事件)的类型都是 SFBool(单值布尔型),它们与 touchBox的事
件出口 isActive和 view2的事件入口 set_bind的类型保持一致。
(3)“url”是脚本节点的一个域,可以直接包含脚本,也可以包含一个或多个
用 URL地址指示的脚本,若有多个地址,则按照先后次序获取第一个可得到的脚
本。
(4)脚本是以函数(function)的形式给出的,函数名 touchBoxIsActive与事
件入口的名称相同,这是和 ECMAScript语言的接口约定,表示相应事件入口收
到事件后调用此函数进行处理。
5.事件流程与小结
下面我们整理一下事件流程:
(1)用户在方块上按下鼠标左键。
(2)接触检测器发出一个“TRUE”事件。
(3)此事件进入脚本节点 touchScript的事件入口 touchBoxIsActive.
(4)调用脚本函数 touchBoxIsActive(注意函数并没有判断入事件的值)。
(5)函数向 touchScript的事件出口 bindView2发送一个“TRUE”事件(还可
以进行其它判断或执行其它事件)。
(6)view2节点收到“TRUE”事件,成为当前视点。按照 VRML约定,“认为”
上述事件是同时发生的,也就是这些事件的时间戳相同。
(7)若用户松开鼠标左键,则接触检测器发出一个“FALSE”事件,此事件同样
引起脚本函数调用并发送“TRUE”事件,所以 view2仍然保持为当前视点。
本节的完整代码是:
#
DEFview1Viewpoint{
position0020
description"view1"
}
DEFview2Viewpoint{
position5020
description"view2"
}
Group{
children[
DEFboxTransform{
translation500
children[
Shape{
appearanceAppearance{
materialMaterial{
diffuseColor100
}
}
geometryBox{}
}
DEFtouchBoxTouchSensor{}
]
}
DEFsphereTransform{
translation000
children[
Shape{
appearanceAppearance{
materialMaterial{
diffuseColor010
}
}
geometrySphere{}
}
]
}
DEFconeTranform{
transaltion-500
children[
Shape{
appearanceAppearance{
materialMaterial{
diffuseColor001
}
}
geometryCone{}
}
]
}
]#endofGroupchildren
}
DEFtouchScriptScript{
eventInSFBooltouchBoxIsActive
eventOutSFBoolbindView2
url"javascript:
functiontouchBoxIsActive(active){
bindView2=TRUE;
}"
}
_bind
这里所建立的虚拟境界并不复杂,但涉及到了 最基础性的功能和概念:
利用检测器产生事件、利用路由传递事件以及利用脚本编写自定义行为,掌握了
这些内容也就掌握了 的核心。在后面的几节中,我们将探索一些专题性
的有趣功能,而本节是基础,因而必须透彻理解。
(六)进一步的完善与修饰
1.邻近传感器
当用户进入或离开邻近检测器所划定的区域时就会触发它。正如你在标准中
可以查到的那样,ProximitySensor节点定义为:
ProximitySensor{
exposedFieldSFVec3fcenter000
exposedFieldSFVec3fsize000
exposedFieldSFBoolenabledTRUE
eventOutSFBoolisActive
eventOutSFVec3fposition_changed
eventOutSFRotationorientation_changed
eventOutSFTimeenterTime
eventOutSFTimeexitTime
}
ProximitySensor节点共有三个外露域( exposedField)和五个出事件
(eventOut).出事件我们已经熟悉,是节点状态发生改变时用来通知其它节点
的,这里的出事件isActive用于ProximitySensor通报自己已被激活。enterTime
和 exitTime通报用户(代表用户的用户化身或指示器)进入和退出
ProximitySensor检测区的时刻。若用户已在检测器之内,则当用户的位置或方
位发生变化时,送出 position_changed和 orientation_changed出事件。这五
个出事件联合起来,就定义了邻近检测器的功能。外露域则集域(Field)、入事
件(eventIn)和出事件(eventOut)三者的功能于一身,也就是说,它既象域
一样描述了节点的当前状态,又可以作为入事件由其它节点修改这种状态,并作
为出事件把这种改变通知其它节点。这里的 enabled外露域是布尔型的,用于
ProximitySensor的启用和停用,center和 size定义形为长方体的邻近检测区。
我们要在方块、球体和圆柱这三个物体构成的静态世界,现在在球体周围增加一
个邻近检测区:
DEFsphereTransform{
translation000
children[
Shape{....}
DEFcomeCloseProximitySensor{
center000
size444
}
]
}
ProximitySensor的名字为 comeCloser,邻近区的中心和球体的球心重合,
形状为正方体,边长为 4 米,是球体直径的两倍。当用户走进球体时就会触发这
个邻近检测器,检测器发出 isActive事件,我们把这个事件出口通过路由指向
Script节点(用来绑定视点 2):
DEFcomeCloserScriptScript{
eventInSFBoolenterProximitySensorIsActive
eventOutSFBoolbindView2
url"javascript:
functionenterProximitySensorIsActive(active){
bindView2=TRUE;
}"
}
随后,我们在邻近检测器的出事件 isActive和脚本节点 comeCloserScript
的入事件 enterProximitySensorIsActive之间建立路由,后者收到事件后执行
函数 enterProximitySensroIsActive,函数发出 bindView2出事件,这个出事件
通过路由连接到视点节点 View2:
Active
_bind
也就是说,一旦用户进入邻近区,境界的当前视点将转换成 View2.这个由
两个视点、三个物体、一个邻近检测器和一个脚本节点组成的境界的完整代码如
下:
#
DEFview1Viewpoint{
position0020
description"view1"
}
DEFview2Viewpoint{
position0020
description"view2"
}
Group{
children[
DEFboxTransform{
translation500
children[
Shape{
appearanceAppearance{
materialMaterial{
diffuseColor100
}
}
geometryBox{}
}
]
}
DEFsphereTransform{
translation000
children[
Shape{
appearanceAppearance{
materialMaterial{
diffuseColor010
}
}
geometrySphere{}
}
DEFcomeCloserProximitrySensor{
center000
size444
}
]
}
DEFconeTransform{
translation-500
children[
Shape{
appearanceAppearance{
materialMaterial{
diffuseColor001
}
}
geometryCone{}
}
]
}
]#endofGroupchildren
}
DEFcomeCloserScriptScript{
eventInSFBoolenterProximitySensorIsActive
eventOutSFBoolbindView2
url"javascript:
functionenterProximitySensorIsActive(active){
bindView2=TRUE;
}"
}
ve
_bind
启动 VRML浏览器进入境界,面向球体一直走过去,当你刚刚感到靠近球体
时,会突然感到自己后退了一大步(或者说物体跳到前方更远的地方),这表明
邻近检测器已经检测到你的靠近,它把这件事通知脚本节点,脚本节点把视点
View2绑定成当前视点,从而使你感到视点突然改变。
再稍稍修改一下邻近检测器,把它的中心位置向右移了 2米:
DEFcomeCloserProximitySensor{
center200
size444
}
这样你就可以从左边(方块那一边)走进球体(视点不跳),但不能从右边(圆
锥那一边)走近它(视点跳转)。
总之,ProximitySensor能够检测用户是否进入或离开检测器指定的空间区域,
典型用法是当用户走进房间时开启灯光,当用户离开时关闭灯光,从而建立功能
丰富的“智能”空间。
2.连续动画
我们已经使用过接触检测器,当我们把鼠标指针放到方块(这个几何节点包
含接触检测器)上面时,指针形状发生变化,这意味着我们已经进入检测区,如
果按下鼠标左钮,则按照我们的定义,当前视点会发生变化。
这一节仍然制作这样一个对接触有反应的方块,只是接触后它会连续不断地转动,
动画行为可以用时间检测器(TimeSensor)驱动,而不断变化的旋转值可用脚本
节点或朝向插补器(orientationInterpolator)给出。
⑴接触传感器
作为开始的基本代码是:
#
DEFcubeTransform{
rotation1110
children[
Shape{
appearanceAppearance{
materialMaterial{
diffuseColor100
}
}
geometryBox{}
}
DEFTouchSTouchSensor{}
]
}
DEFrevolverScript{
eventInSFBoolstartRevolving
eventOutSFRotationrevolve
fieldSFFloatangle0
url"javascript:
functionstartRevolving(){
revolve[0]=1;
revolve[1]=1;
revolve[2]=1;
revolve[3]=angle;
angle+=;
}"
}
_rotation
其中,方块 cube包含两个子节点,前者定义了它的形态(红色的单位立方体),
后者把它定义成接触检测器。注意,cube的类型是 Transform节点,它的 rotation
域是外露域,指定本组相对于上层坐标系的旋转值,这里指定的初始值是
“1110”,其中前三个数值定义旋转轴,最后一个值定义旋转角。由于它是外露
域,因而可以通过入事件(名为 set_rotation)进行修改,下面定义的动态行
为就是这样实现的。
Script节点 revolver的核心是内联的 ECMAScript脚本函数。它给定一个不断
变化的旋转值。当鼠标指针移动到方块之上时,接触检测器发出 isOver,和第一
节中采用的 isActive事件不同,isOver不需鼠标左钮按下时即可发出。isOver
事件通过路由传递给脚本节点的事件入口 startRevolving,从而启动函数
startRevolving,函数将一个新的旋转值发往事件出口 revolve,这个旋转值通
过路由进入 cube的外露域 rotation,修改了方块的旋转角,引起它的朝向变化。
鼠标指针在 cube上面的每次方位变化都引起 isOver事件发送一次,从而导致方
块旋转一次。
⑵时间传感器
为了使方块能够连续旋转,需要引进等间隔连续发送的时间序列,这正是时间检
测器的用武之地。时间检测器随着时间推移不断产生事件,可用于多种目的,包
括:
a.驱动连续性的仿真和动画
b.控制周期性的活动(如每分钟一次)
c.初始化单独事件,如报警钟
下面是我们要用的时间检测器和修改后的路由关系:
DEFtickerTimeSensor{
loopTRUE
enabledFALSE
}
_enabled
_rotation
enabled用于启用和停用时间检测器,开始时它处于停用状态,以后由接触检测
器的 isOver事件修改这一状态。启用的时间检测器每隔 秒送出一个
cycleTime事件,并用它来触发 revolver的 startRevolving事件,注意,
cycleTime事件的类型为 SFTime,而路由两端事件的类型必须匹配,所以尽管这
里我们不关心这个事件表示的具体时刻,还是把 revolver的 startRevolving事
件类型也改为 SFTime.这样,revolver的函数 startRevolving()就会每 秒
调用一次,从而驱动方块连续旋转。完整的代码是:
#
DEFcubeTransform{
rotation1110
children[
Shape{
appearanceAppearance{
materialMaterial{
diffuseColor100
}
}
geoemtryBox{}
}
DEFTouchSTouchSensor{}
]
}
DEFrevolverScript{
eventInSFTimestartRevolving
eventOutSFRotationrevolve
fieldSFFloatangle0
url"vrmlscript:
functionstartRevolving(){
revolve[0]=1;
revolve[1]=1;
revolve[2]=1;
revolve[3]=angle;
angle+=;
}"
}
DEFtickerTimeSensor{
loopTRUE
enabledFALSE
}
_enabled
_rotation
上述脚本节点的功能比较简单,只是不断送出调整的旋转值,它是关键帧动画的
一种。由于关键帧动画十分常用,故 VRML专门定义了插补器节点来实现它。
⑶方位插值器
插补器节点可认为是 VRML内置的脚本节点,它们执行简单的动态计算,通常和
时间检测器或者能够使对象产生动作的节点结合在一起使用,生成线性关键帧动
画。插补器节点实际上是一个由关键点和对应关键值定义的分段线形函数。根据
插值类型的不同,VRML共定义六个插补器节点:
ColorInterpolator(颜色插补器)、
CoordinateInterpolator(坐标插补器)、
NormalInterpolator(法线插补器)、
OrientationInterpolator(朝向插补器)、
positionInterpolator(位置插补器)、
ScalarInterpolator(标量插补器)。
所有插补器的域和事件都是类似的:
eventInSFFloatset_fruction
exposedFieldMFFloatkey[...]
exposedFieldMF[type]keyValue[.....]
eventOut[S|M]F[type]keyValue_changed
关 键 值 域 keyValue的 类 型 决 定 了 插 补 器 的 类 型 ( 例 如 ,
OrientationInterpolator的 keyValue域 的 类 型 是 MFFloat) .入 事 件
set_fraction接收 SFFloat型的事件,插补器随即根据它进行插值,并通过出
事件 value_changed送出插值结果。
这里我们把时间检测器的 fraction_changed事件作为插补器的输入,这个事件
是一个[0,1]区间的值,每个时间步都送出一次,表示当前周期内已过去的时间
相对于整个周期的比例,是插补器常用的输入源之一。与此对应,我们把插补器
关键帧的取值也定义在[0,1]范围内。与 0和 1这两个关键帧对应的关键值的旋
转轴是相同的,只是旋转角度不同(0,),这样方位插补器输出的旋转值
的旋转轴固定不变,旋转角从 0递增到 ,然后不断重复。
#
DEFcubeTransform{
rotation1110
children[
Shape{
appearanceAppearance{
materialMaterial{
diffuseColor010
}
}
geometryBox{}
}
DEFTouchSTouchSensor{}
]
}
DEFrevolverOrientationInterpolator{
key[0,1]
keyValue[,]
}
DEFtickerTimeSensor{
cycleInterval2
loopTRUE
enabledFALSE
}
_enabled
_fraction
_rotation
小结:本节实现连续动画,动画由接触检测器启动,由时间检测器驱动,动画本
身比较简单,就是不断地旋转。产生不断变化的旋转值的方法有两种:自己编写
脚本,或者利用插补器节点。
3.动态修改场景图
场景图是描述境界结构的基本概念,节点是构成场景图的基本单元。组节点
是能够
包含子节点的节点,组节点本身还可作为其他组节点的子节点,从而形成层次性
体系结
构。VRML中的组节点包含 Anchor(锚)、Billboard(布告牌)、Collision(碰撞)、
(Group)(组)、
Inline(内联)、LOD(细节层次)、Switch(开关)、Transform(变换)共8种,除Inline、
LOD、Switch这几个具有特殊功能外,它们都定义了人事件 addChild和
removeChildren,前者用
于向组节点的子节点域 children中增加新的子节点,后备用于从中删除子节点,
这样就可
以动态修改场景图的结构。
下面是我们这一节要建立的境界,开始的时候球体位于左边红色方块的内部,
在按动底部的绿色方块后,球体进人右边蓝包入块之内。
#
Viewpoint{position0015}
DEFleftBoxTransform{
translation-500
children[
Shape{
appearanceAppearance{
materialMaterial{diffuseColor100}
}
geometryBox{}
}
]
}
DEFrightBoxTransform{
translation500
children[
Shape{
appearanceAppearance{
materialMaterial{diffuseColor001}
}
geometryBox{}
}
]
}
DEFonoffTransform{
translation0-50
children[
Shape{
appearanceAppearance{
materialMaterial{diffuseColor010}
}
geometryBox{}
}
]
}
其中,左边的方块为红色,右边的方块为蓝色,下边的方块为绿色,都是
Transform类
型,三者都位于场景图的最高层,都是场景图的根节点,都包含一个 Box几何体
作为子节
点。下面为红方块增加一个球体子节点:
DEFleftBoxTransform{
translation-500
children[
Shape{
appearanceAppearance{
materialMaterial{diffuseColor100}
}
geometryBox{}
}
DEFSphereChildShape{
appearanceAppearance{
materialMaterial{diffuseColor101}
}
geometrySphere{}
}
]
}
为了以后引用方便,这里为球体子节点起了名字:SphereChild。为了让用户
能够增删这个儿子,把绿方块定义成接触传感器:
DEFonoffTransform{
translation0-50
children[
Shape{
appearanceAppearance{
materialMaterial{diffuseColor010}
}
geometryBox{}
}
DEFTSTouchSensor{}
]
}
子节点增删的具体任务由 Script节点来完成:
DEFSScript{
eventInSFBoolisActive
eventOutMFNodechild
fieldMFNodetestNodeUSESphereChild
url”javascript:
functionisActive(value){
if(value)child=testNode;
}”
}
请注意,它的出事件 child的类型是 MFNode,也就是说,通过这个事件送
出的是节点。
节点 S的 testNode域是对球体 SphereChild引用(USE语句),引用不复制该节
点,而是把同—节点再次插入场景图,从而导致 SphereChild拥有多个父亲,所
以场景图仪仅是层次结
构,而不是树形结构。加上下面的路由语句,建立事件联系:
接触传感器 TS的激活事件 isActive链接到脚本节点 S的 isActive,这样
用户一旦按动绿方块,就会启动脚本节点的事件处理函数 isActive(),此函数
把 testNode节点(即球体节点 SphereChild)送至出事件 。根据路由,左
边 红 方 块 的 事 件 人 口 收 到 此 事 件 , 按 照
removeChildren的语义,球体节点 SphereChild从 leftBox的子节点列表中删
除。与此同时,右边蓝方块的事件入口 也收到
出事件,根据 addChildren的语义,球体节点 SphereChild加入 rightBox的子
节点列表。通过这个过程,球体节点 SphereChild的父节点从 leftBox更换成
rightBox。
4.扩充节点类型
VRML提供了 54种节点类型,称为内部节点类型。然而实际应用中可能要求
新的节点类型,原型(prototype)是 VRML实现节点类型扩充的基本机制。新节点
类型是根据已定义的(内部的或原型的)节点类型定义的,一旦定义,原型节点类
型就可以像内部节点类型—样在场景图中实例化。原型可以在当前文件中定义并
使用,也可以在其他文件中定义,即外部原型。外部原型提供了一种使节点类型
能够跨越网络的机制。本节的原型例子取自 VRML97标准,它定义的是—个桌子
类型,这个原型为:
#
PROTOTwoColorTable[
]
{
Transform{
Children{
Transform{#桌面
children[
Shape{
appearanceAppearance{
materialMaterial{diffuseColorIStopColor}
}
geometryBox{}
}
]
}
Transform{
children[
DEFLegShape{
appearanceAppearance{
materialMaterial{diffuseColorISlegColor}
}
geometryCylinder{}
}
]
}
Transform{#另一条桌腿
childrenUSELeg
}
Transform{#另一条桌腿
childrenUSELeg
}
Transform{#另一条桌腿
childrenUSELeg
}
]#根节点 Transform的儿子结束
}#根节点 Transform结束
}#原型结束
原型语句 PROTO分为原型接口声明和原型定义两部分。接口声明包括原型的
入事件和出事件的类型和名称,以及原型的域的类型、名称和缺省值。这里的接
口声明为:
PROTOTwoColorTable[
]
这个原型的类型名称为“TwoColorTable”(双色桌),它有两个域;
legColor(桌腿色)和 topColor(桌面色)。作为节点类型,TwoColorTable的用法
和其他内部节点类型—样。
接口声明之后是原型的主体,称为原型定义。原型定义实际上是一个场景图,
由一个或多个根节点、嵌入的 PROTO语句和 ROUTE语句构成,其中的第一个节点
类型确定原型实例在 VRML文件中的使用方法。例如,如果原型定义中的第一个
节点是 Material节点,则只要可以使用 Material节点的地方,原型实例都可以
使用。原型定义中定义的其他节点及其附带的场景图都不进入原型实例所在的变
换层系,但可以被原型定义中的 ROUTE语句或 Script节点引用。TwoColorTable
原型中的第一个节点是 Transform组节点,它决定了 TwoColorTable型节点在场
景图中的方法,在场景图中添加一个 TwoColorTable型节点.相当于增加
Transform。
原型定义中节点的域、人事件、出事件可以通过 IS语句和接口声明中的域、
入事件、出事件建立关联,关联实际上相当于把原型定义中的这些域和事件公开
作为原型的域和事件。关联的基本规则是域和域、入事件和入事件、出事件和出
事件对应关联,原型定义中的外露域可以和接口声明中的域、入事件、出事件或
外露城关联。本例中的关联有两个:桌面 diffuseColor域和接口声明中的
topColor域,桌腿的 diffuseColor域和接口声明中的 legColor域之间。也就
是说,TwoColorTable型节点中的 topColor和 legColor值实际上分别确定了桌
面和桌腿的漫反射色 diffuseColor。