Java3D用其自己定义的场景图和观察模式等技术构造了3D的上层结构,实现了在Java平台使用三维技术。本文在原理上着重介绍Java3D特有的两个重要概念:场景图(Scene Graph)、观察模式(View Model)。在接口使用上的介绍分为两部分:实例说明如何使用Java3D接口;说明如何将Java3D技术与Java原有的Web技术(JSP、Serverlet)相结合,在网页上实现三维显示。
1. 关于Java3D 

1.1 Java3D简介

Java3D API是Sun定义的用于实现3D显示的接口。3D技术是底层的显示技术,Java3D提供了基于Java的上层接口。Java3D把OpenGL和DirectX这些底层技术包装在Java接口中。这种全新的设计使3D技术变得不再繁琐并且可以加入到J2SE、J2EE的整套架构,这些特性保证了Java3D技术强大的扩展性。 

JAVA3D建立在JAVA2(JAVA1.2)基础之上,JAVA语言的简单性使JAVA3D的推广有了可能。它实现了以下三维显示能够用到的功能: 

生成简单或复杂的形体(也可以调用现有的三维形体) 
使形体具有颜色、透明效果、贴图。 
在三维环境中生成灯光、移动灯光。 
具有行为的处理判断能力(键盘、鼠标、定时等)。 
生成雾、背景、声音。 
使形体变形、移动、生成三维动画。 
编写非常复杂的应用程序,用于各种领域如VR(虚拟现实)。 
J2SE在其标准开发包中并不提供Java3D的API,Java3D是一个独立的可选组件,可以单独下载。Java3D现在(截止到2003年5月)提供的正式版本是1.3.0,可以在 http://java.sun.com/products/java-media/3D/download.html 下载该开发工具包。 

Java3D 1.3有7个不同的可下载版本:

Java3D for Windows(DirectX version)SDK for JDK(include Runtime);
Java3D for Windows(OpenGL version)SDK for JDK(include Runtime);
Java3D for Solaris/SPARC Runtime for JDK(include Runtime);
Java3D for Windows(DirectX version)Runtime for JRE;
Java3D for Windows(OpenGL version)Runtime for JRE;
Java3D for Solaris/SPARC SDK for JRE;
Java3D for Solaris Runtime 64 bit support; 

其中前三个版本是开发包。第4、5、6个版本是Java3D运行期支持程序包。最后一个是支持Solaris 64 bit操作平台运行Java3D的程序包。前三个的开发包包含了各自对应的运行期支持包。Java语言本身具有跨平台特性,无论使用上述哪个版本的开发包,概念和实现代码都会保持完全一致。这里我们使用Java3D for Windows(OpenGL version)SDK for JDK(include Runtime);版本作为我们讨论、实现Java3D的工具包。注意在安装此版本SDK前要保证下列环境: 

Java 2 (Runtime or SDK) version 1.3.1 或更后的版本 

OpenGL 1.1 或更后的版本,并且是Microsoft支持的显卡厂商 

Windows NT 4.0 only: Service ack 3 或更后的版本(Window2000、WindowXP) 

1.2 Java3D与其他三维技术的比较

JAVA3D可应用在三维动画、三维游戏、机械CAD等多个领域。但作为三维显示实现技术,它并不是唯一选择而且是一个新面孔。在Java3D之前已经存在很多三维技术,这些三维技术在实现的技术、使用的语言以及适用的情况上各有不同,我们主要介绍与Java3D又密切关系的三种技术:OpenGL、DIRECT3D、VRML 

OpenGL是业界最为流行也是支持最广泛的一个底层3D技术,几乎所有的显卡厂商都在底层实现了对OpenGL的支持和优化。OpenGL同时也定义了一系列接口用于编程实现三维应用程序,但是这些接口使用C(C++)语言实现并且很复杂。掌握针对OpenGL的编程技术需要花费大量时间精力。 

DIRECT3D是Microsoft公司推出的三维图形编程API,它主要应用于三维游戏的编程。众多优秀的三维游戏都是由这个接口实现。与OpenGL一样,Direct3D的实现主要使用C++语言。 

VRML2.0(VRML97)自1997年12月正式成为国际标准之后,在网络上得到了广泛的应用,这是一种比BASIC、JAVASCRIPT等还要简单的语言。脚本化的语句可以编写三维动画片、三维游戏、计算机三维辅助教学。它最大的优势在于可以嵌在网页中显示,但这种简单的语言功能较弱(如目前没有形体之间的碰撞检查功能),与JAVA语言等其它高级语言的连接较难掌握,因而逐渐被淹没在竞争激烈的网络三维技术中。 

表1是Java3D与其它三维技术的比较图,可以从中直观的看出他们相互间的区别: 

技术 实现层次 开发技术(难度) 扩展性 最适合应用领域 
Java3D 中层(JVM) Java(较易) J2SE标准扩展(好) 网上三维显示实现… 
OpenGL 底层(显卡) C\C++(难) 各大厂商支持(较好) 三维设计软件… 
Direct3D 底层(操作系统) C++(较难) Windows平台(差) 三维游戏… 
VRML 上层(网页) 标记语言(容易) 安装插件支持(一般) 网上虚拟现实… 


表1:3D技术对招表

2 Java3D的场景图结构
Java3D实际上是Java语言在三维图形领域的扩展,与Java一样,Java3D有纯粹的面向对象结构。Java3D的数据结构采用的是Scene Graphs Structure(场景图),就是一些具有方向性的不对称图形组成的树状结构(图1)。 

我们在一个Java3D应用程序看到的逼真三维场景从程序的角度看来,实际就是由Java3D定义的一系列的对象,这些对象不是杂乱无序,对象之间也不是毫无关系。如果想让三维图像正常显示,必须在这两点上遵循Java3D场景图的规定。观察图1,Java3D场景图的树结构由各种各样的对象组成: 

在图中出现的这些对象都实现了Java3D中有重要的意义的类,从逻辑上我们将它们分为三类:

根节点(Root):Virtual Universe Object 
节点(Node):Local Object、Branch Group Nodes、Behavior Node、Shape3D Node… 
叶子节点(Leaf):Appearance、Geomery.. 


图1:在应用中的Java3D场景图

场景图中线和线的交汇点称为节点(Node),这些节点都是Java3D类的实例(Instance of Class),节点之间的线表示各个实例之间的关系。

Virtual Universe是根节点,每一个场景图的Virtual Universe是唯一的。 
在Virtual Universe下面是Locale节点,每个程序可以有一个或多个Locale,但同时只能有一个Locale处于显示状态,就好象一个三维世界非常大,有很多个景点,但我们同时只能在一个景点进行观察。Java3D允许从一个Locale跳到另一个Locale,不过绝大多数程序只有一个Locale。 
每一个Locale可以拥有多个BranchGroup节点。所有三维形体的其位置信息(Transform Group Nodes)都建立在BranchGroup节点之上。 
TransformGroup Node用来设定Shape3D在Virtual Universe中的位置。 
Spape3D Node是三维图形节点,这个节点的实体放映在最后的显示画面中,就是三维世界中的每个形体。包括正方体、球体以及任何形状和外观的三维形体。 
位于场景图最下层的是两个叶子节点:三维体的外观(Appearance)和几何信息(Geometry),这两个节点定义了一个三维体的显示效果。 
View latform位于图1的另一个分枝上,与前面所有描述三维体的性质的概念不同,View latform和View都是用来定义观察者的信息。 
上面所列的概念很多,但是对于建立一个简单的Java3D程序,我们至少需要了解三个概念:虚拟宇宙(Virtual Universe)、场景(Locale)、坐标系统。 

2.1 虚拟宇宙(Virtual Universe)

在Java3D中,虚拟宇宙被定义为结合一系列对象的三维空间。虚拟宇宙被用作最大的聚集体表现单位,同时也可被看作一个数据库。不管是在物理空间还是逻辑内容,虚拟宇宙都可以很大。实际上在大多数情况下,一个虚拟宇宙就可以满足一个应用程序所有的需求。 

虚拟宇宙是各自独立的个体,原因是在任何时候一个结点对象都不能在超过一个的虚拟宇宙中存在。同样的,在一个虚拟宇宙中的结点对象也不能在其他的虚拟宇宙中可见或者与其他的对象结合。 

对于一个Java3D应用程序,必须定义一个虚拟宇宙才可以在这个"宇宙"中显示三维图像。 

2.2 Java3D的坐标系统

默认情况下,Java3D的坐标系统是右旋的,用方位语义学来解释就是:正y方向是本地重力的上,正x方向是水平的右,正z是这对着观察者的方向。默认的单位是米。 

双精度浮点、单精度浮点甚至是定点来表示的三维坐标都足够来表示和显示丰富的3D场景。不幸的是,场景不是真实世界,更不必说整个宇宙了。如果使用单精度坐标,有可能出现下列情景: 

离原点仅有一百公里的距离,被描绘得相当量子化,所能达到的最好效果就是三分之一英寸,在实际应用中这样的精度比要求的粗糙的多。 
如果要缩小到一个很小的尺寸(例如表现集成电路的大小),甚至在离原点很近的地方就会出现同坐标问题。 
为了支持一个大型的邻接虚拟宇宙,Java3D选择了有256位的高分辨率坐标:

Java3D高分辨率坐标由三个256位的定点数组成,分别表示x、y、z。定点被固定在第128位,并且值1.0被定义为真实的1米。这个坐标系统足够用来描述一个超过几百万光年距离的宇宙,也可以定义小于一质子大小(小于一普朗克长度)的对象。 

在Java3D中,高分辨率坐标仅仅用于将更加传统的浮点坐标系统嵌入更高分辨率的底层系统。用这种方法,可以创造出一个具有任意大小和规模的在视觉上无缝的虚拟宇宙,而且可以不必担心数字上的精度。(参看表2) 

一个256位的定点数还具有能够直接表示几乎任何的合理适当的单精度浮点值。 

Java3D用有符号的、两位补码的256位定点数字来表示高分标率坐标。尽管Java3D保持内部高分辨率坐标表示的不透明,但用户用有八个整型变量的数组来表示256位的坐标。Java3D把数组中从索引号由0到7分别看作高分辨率坐标的从高到底位上的数。第128位上是二进制的小数点,也可以说在索引号为3和4的整数之间。高分辨率坐标的1.0就是1米。 

如果是"小"的虚拟宇宙(类似于相对比例的几百米),在虚拟宇宙对象下的(0.0,0.0,0.0)点建立一个带有高分辨率坐标的Locale作为根节点就足够使用了;装入程序在装入过程中能自动构建结点,而在高分辨率坐标下的点不需要任何外部文件的直接描述。 

大一些的虚拟宇宙期待被构建为有如同计算机文件那样的层次,
3 Java3D的观察模式
Java3D引入了一种新的观察模式,这种模式使Java编写的显示效果符合"编写一次,随处运行"的原则。Java3D还把这种功能推广到显示设备或六等级自由度输入外部设备,例如跟踪摄像头。这种新的观察模式的"一次编写,随处观察"的特性意味着用Java3D观察模式编写的应用程序和Applet可以广泛应用于各种各样的显示设备。在不修改场景图的条件下,图像可以在包括标准电脑显示、多放射显示空间和安装摄像头设备的显示设备上被渲染。这也意味着在不需要修改场景图的情况下,同一个应用程序既能够渲染立体景象,还能通过摄像头的输入控制渲染过的观察图。 

Java3D的观察模式通过完全分离虚拟和现实世界来实现这种多功能性。这种模式区分了以下两种情况:

一个应用程序通过控制观察平台的位置和方向在虚拟宇宙中对一个观察台对象(ViewPlatform)定位、定向和设定比例尺; 
渲染器使用已知位置和方向计算出要使用的观察对象,对终端用户物理环境的描述确定用户在物理环境中的位置和方向。 
3.1 为什么使用一个新的模式
在底层的编程接口中可以找到基于照相机的观察模式,开发者通过它可以控制所有渲染图的参数。它可以应付处理常规的应用程序,但是处理有更广阔的适应性的系统的时候就显得力不从心,这些系统包括:把整个世界作为一个单元装入和显示的观察器或浏览器、可供终端用户观察、操纵、显示、甚至与虚拟世界交互的系统。 

基于照相机的观察模式仿效在虚拟世界中放置一个照相机而不是一个人。开发者必须持续重新配置一个照相机来模拟"在虚拟世界中有一个人"。 

Java3D观察模式直接和跟踪摄像头结合。在有摄像头的情况下,用户会有好像他们是真实的存在在那个虚拟世界的错觉,而开发者可以不做任何附加的工作就可以为用户带来这种效果。 

在没有摄像头并且只是用来表现一个单一的标准显示的情况下,Java3D观察模式表现得更像传统的基于照相机的观察模式,只是加上了能够产生立体透视图的功能。 

在一个需要由物理环境规定一些观察参数的系统中,让应用程序来控制所有的观察参数并不合理。 

例子就是:一个带有摄像头的显示设备可以用其光系统直接决定应用程序中的观察领域。不同的设备有不同的光系统,程序开发者硬绑定这样的参数或允许终端用户改变这样的参数都是不合理的。 

另外一个例子是:一个可以由用户当前的头部位置自动计算出观察参数的系统。只有一个对世界的说明和一条预先定义的轨迹可能不会严密的定义一个终端对象的观察。对于有摄像头设备用户,他们可能会期待在沿着一条固定的路线前进的时候能够看到他们左右两旁的物体。就好像在一个游乐场中,游客乘坐观光车按照固定的路线参观游乐场,但是在这过程中,游客可以持续转动他们的头。 

由于依靠终端用户的具体物理环境,观察的各个参数,尤其是观察和投影的基体变化很大。影响观察和投影基体的因素包括显示设备的物理尺寸,显示设备的安装方法(在用户的桌面或用户的头顶上),计算机是否知道用户的头在三维空间的位置,头顶装置真实的观察领域,显示设备上每平方英寸的像素数,还有其他类似的参数。 

Java3D建立的观察模式完全可以满足上述所有的需求。 

3.2 分离物理和虚拟
Java3D分离了虚拟环境和物理环境:应用程序在虚拟环境中按照一定关系放置对象,而用户存在在物理环境之中,看计算机显示器并操纵输入设备。 

Java3D也定义了用户所在的物理世界和图像程序所在的虚拟世界之间最基本的通信。这种"物理到虚拟世界"的通信定义了一个单一的公共空间,在这个空间中用户的动作影响虚拟世界中的对象,而在虚拟世界中的任何活动都会影响最终用户的观察。 

虚拟世界是虚拟对象存在的通用空间。虚拟世界的坐标系统相对于每个Locale对象的高分辨率坐标存在,它定义了所有附加于这个Locale的虚拟世界坐标原点。包含当前活动的观察平台对象的Local定义了用来绘图的虚拟世界坐标。Java3D最后把所有图像单元的坐标转换到通用的虚拟世界空间中。 

物理世界就是指真实的世界。这是真实的用户存在和移动他(她)的头和手的空间。这也是使用任何物理追踪仪可以定义他们的局部坐标和几个标准的坐标系统被描述的空间。 

物理世界是一个空间,而不是Java3D不同的程序执行实例之间的通用坐标系统。所以当两个不同的计算机在世界上两个不同的地方同时运行同一个程序的时候,Java3D中没有直接来描述它们在物理世界坐标系统中相对位置的机制。因为标准问题,当地的跟踪系统仅仅定义了特定的Java3D应用程序实例的物理坐标系统。 

3.3 Java3D中用来定义观察的对象
Java3D通过几个对象来发布它的观察模式。特别是View对象和与它相关的组件对象:PhysicalBody对象、PhysicalEnvironment对象、Canvas3D对象、Screen3D对象。图3描述了View对象的中心角色和组件对象的辅助角色。 

观察有关的对象都在图3中,它们起的作用如下: 

ViewPlatform(观察平台):一个view用一个叶子结点来在场景图为自己定位。观察平台的起始结点指定了它的位置、方向和在虚拟世界中的比例尺。 

View(观察):主要的观察对象包含了很多观察的状态。 

Canvas3D:抽象窗口工具箱中画布对象的3D版本。它描绘了一个可以让Java3D在上面画图像的窗口。它包括了一个对Screen3D对象的引用和描述一个Canvas3D要用到的尺寸、形状和位置信息。 

Screen3D:一个包含描述显示荧屏物理属性信息的对象。Java3D把显示荧屏信息分别放在单独的对象中,这样做可以防止在每一个Canvas3D对象中不同的显示屏幕信息共享一个屏幕。 

PhysicalBody:一个包含刻度信息的对象,它描述了用户的物理身体。 

PhysicalEnvironment:一个包含刻度信息的对象,它描述了物理世界。主要的信息描述了环境的六自由度硬件。 



图3:View和它的组件对象以及它们的相互联系

这些对象一起描述观察的几何体胜于明白的提供观察或投影基体。Java3D的表现工具用这个信息来构造适合的观察和投影基体。这些观察对象的几何中心为产生一个观察提供了更大的弹性,这种弹性需要支持可以选择的显示配置。 

3.4 ViewPlatform: 在虚拟世界中的位置
一个Viewplatform结点定义了一个坐标系统。这样,在虚拟世界中就有了一个有原点或参考点的参考系。观察平台是一个附加在观察对象的点并且作为决定描绘工具观察的基础。 

图4表示了一个场景图的一部分,它包括一个观察平台结点。直接在观察平台之上的结点决定了它在虚拟世界中的位置和方向。应用程序和或行为通过修改直接在观察平台之上任何与TransformGroup结点结合的Tramsform3D对象可以在虚拟世界中任意移动VierPlatform。一个简单的应用程序可能直接在一个观察平台上定义一个TransformGroup结点。 

一个虚拟宇宙可能有很多不同的观察平台,但是一个特定的View对象只能附加于一个单一的观察平台之上。这样,每个画在Canvas3D上的图画都是从一个单一的观察平台开始。 



图4:包含观察平台的一部分场景图

3.5 如何在虚拟世界中移动

应用程序通过修改观察平台的上级TransformGroup在虚拟世界中航行。修改一个观察平台的位置和方向的应用程序的例子包括:浏览器、提供航行控制的阅读器、做建筑预设计的程序、甚至是搜寻和毁坏游戏。 

控制观察平台对象能产生很有趣和有用的结果。我们可以定义一个简单的场景图,这个程序的目的是在窗口的正中画了一个对象并且绕自己的中心转动。 

我们不管在中心的对象,而让ViewPlatform在虚拟世界中绕圈。如果形体结点包括一个地球模型,这个程序可能产生一个类似于绕地球的远航员观察对象。 

如果在这个世界中加入更多的对象,这个场景图允许经由行为结点来浏览整个虚拟世界。 



图5:一个由观察控制的简单场景图 

应用程序和
4 将Java3D与Java平台技术的结合
Java3D可以很容易的与Java平台的其他技术相结合,如Applet、JSP、Serverlet、JDBC、EJB等。100%的纯Java实现是Java3D可以与如此多的Java平台技术结合的根本原因: 

同是Java平台保证Java3D可以在Applet中实现; 
Applet使Java3D可以轻易的在网页中显示; 
JSP、Serverlet技术保证将动态网页技术用于Java3D显示; 
Serverlet本身就是J2EE平台的核心技术,这使得Java3D可以搭建于J2EE平台。更可以使用所有J2EE的其他技术:JDBC、EJB、JMS… 
4.1 在网页上显示3D图形

Java3D一个最大的特性是可以使用Applet作为显示容器,例3和例4的代码分别显示了如何在Applet中显示3D图形和在网页文件中(HTML)嵌入该Applet。 


Public class HelloUniverse extends Applet { 
private SimpleUniverse u = null;   
public BranchGroup createSceneGraph() { 
BranchGroup objRoot = new BranchGroup();    
…. 
return objRoot;  
}   
public void init() {
Canvas3D c = new Canvas3D(config); 
add("Center", c);   
….
u.addBranchGraph(scene);   
}  
….
}

 


例3 Applet实现Java3D


<HTML>
<HEAD>
<TITLE>Hello, Universe!</TITLE>
</HEAD>
<BODY BGCOLOR="#000000">
<applet align=middle code="HelloUniverse.class" width=256 height=256>
</applet>
</BODY>
</HTML>

 


例4 在网页嵌入显示3D Applet

4.2 动态网页技术与Java3D

通过Jsp和Serverlet,可以使Java3D在网页中"动"起来。虽然Java3D本身就有三维动画的功能,但是这里的"动"指得是赋予了程序编写人员对Java3D动态的控制能力。改造上面的HelloUniverse,例5 的jsp代码可以实现控制旋转的正方体大小的功能。通过每次Random对象生成的随机数,立方体的大小也是随即改变,这段程执行的效果,如图6、7所示。 


<%@ page contentType="text/html; charset=GBK" %>
<HTML>
<HEAD>
<TITLE>Hello, Universe!</TITLE>
</HEAD>
<BODY BGCOLOR="#000000">
<%int i=300;%>
<%float j=i*(new java.util.Random()).nextFloat();%>
<applet align=middle code="HelloUniverse.class" width=<%=j%> height=<%=j%>>
</applet>
</BODY>
</HTML>

 


例5实现可以动态调整三位物体大小的jsp代码



图6 Jsp显示效果1



图6 Jsp显示效果2

4.3 J2EE平台对Java3D的支持

上面的例子只是通过动态设定Applet大小来控制Java3D的显示,实际上可应通过更多的方法实现Jsp、Serverlet对Java3D显示效果的控制,甚至可以将Java3D置于J2EE平台的显示层,实现对EJB、JDBC的调用。 

实现Java3D利用J2EE平台资源的方法很多,甚至可以直接在Java3D的实现类中直接调用EJB。但是从J2EE平台的设计模式出发,把对EJB调用放到Jsp中,而将返回的结果作为参数传入实现Java3D的Applet类中是一个更好的模式。具体代码见例6。 


<%@ page contentType="text/html; charset=GBK" %>
<%@ page import="javax.ejb.*" %>
<%@ page import="java.rmi.*"%>
<%@ page import="javax.rmi.PortableRemoteObject"%>
<%@ page import="javax.naming.*"%>
<%@ page import="java.util.Properties"%>
<HTML>
<HEAD>
<TITLE>Hello, Universe!</TITLE>
</HEAD>
<BODY BGCOLOR="#000000">
<% try{   
Properties properties = null;  
Context context = new InitialContext(properties);   
Object ref = context.lookup("Customer");   
EJBHome home= (EJBHome)PortableRemoteObject.narrow(ref,CustomerHome.class);  
CustomerHome home=(CustomerHome)home;    
Customer customer= home.findByPrimaryKey("1000000");   
String data = customer.getData();
}
catch(Exception e){e.printStackTrace();}
%>
<applet align=middle code="HelloUniverse.class" width=200 height=200>
<param name="Data" value=<%=data%>>
</applet>
</BODY>
</HTML>