用Java开发3D游戏之创建场景

本文将用一个Java 3D游戏来介绍Checkers3D以及如何使用它创建一个场景,该场景包含深绿色和蓝色间隔的平铺表面,其轴向是沿着x和z轴,一个蓝色的背景和一个浮动的可以从两个不同的方向照亮的球体。用户(观察者)能通过移动鼠标移动该场景。

  图1中左边的快照显示出程序起始的视图;右边的显示出用户移动一点后的场景图。


图1.起始和后来的视图


   Checkers3D游戏展示了Java 3D编程中许多共同之处及一些技巧。例如,3D场景的显示是使用Java 3D Canvas3D类完成的-这个类必须与Java的Swing组件集成到一起。所有的Java 3D应用程序要求一个场景图,而Checkers3D展示了怎样添加基本的形状、光源(环境光和有向光)和背景。该场景图用作文档的一种可视化形式,并且 借助于Daniel Selman的Java3dTree包可以容易地生成其信息的一种文本版本。

  地板和球利用了Java 3D的QuadArray、Text2D和球体几何体类。地板是在一个QuadArray中的一系列的四边形;而标签是利用Text2D对象沿着地板的主 轴放置的。球体的实现将向用户展示怎样着色,点亮和放置一个3D形状。用户从一种视图来观察该3D世界。你将看到如何在初始化过程中确定球体的位置,以及 如何在执行期间通过使用Java 3D的OrbitBehavior类来移动该球体的。

  一、 Checkers3D的类图

  图2中的类图显示该Checkers3D应用程序的所有的公共和私有数据及方法。


图2.Checkers3D的类图


   Checkers3D是该应用程序最顶层的JFrame。WrapCheckers3D是一拥有场景图的JPanel,该场景图可经由一 Canvas3D对象来观看。 CheckerFloor创建地板子图(如瓷砖,轴,等等),这里相同颜色的瓷砖是用一个ColoredTiles对象描述的。

  二、 集成Java 3D和Swing

  Checkers3D是一个JFrame-如果必要的话,可以把GUI控件,例如Swing文本域和按钮等放置到它上面。在本文的实例中,我创建了一个WrapCheckers3D(一个JPanel)的实例并把它放到一个BorderLayout的中央:

c.setLayout( new BorderLayout( ) );
WrapCheckers3D w3d = new WrapCheckers3D( );//3D画布的面板
c.add(w3d, BorderLayout.CENTER);


  在该场景上的Canvas3D视图是在WrapCheckers3D中创建的:

public WrapCheckers3D( ){
setLayout( new BorderLayout( ) );
//另外的初始化代码
GraphicsConfiguration config =SimpleUniverse.getPreferredConfiguration( );
Canvas3D canvas3D = new Canvas3D(config);
add("Center", canvas3D);
//另外的初始化代码
}


   当使用Canvas3D时必须小心,因为它是一个轻量级的GUI元素(在一个OS生成的窗口之上的薄层)。重量级的组件无法容易地与轻量级的Swing 控件相结合;这些控件大部分由Java生成。如果把Canvas3D对象嵌入到Jpanel中就可以避免这些问题;那么该面板就可以安全地与基于 Swing构建的应用程序的其它部分集成到一起。

  提示 在j3d.org(http://www.j3d.org/tutorials/quick_fix/swing.html)上有关于把Canvas3D和Swing相结合的详细讨论。

  与前面的章节中的应用程序相比,这里没有更新/绘制动画循环。这是不必要的,因为Java 3D包含它自己的机制来监视场景变化并且初始化着色。下面是该算法的伪码形式:

while(true){
处理用户输入;
if (存在请求) break;
执行行为;
if (场景图发生变化)
遍历场景图并着色;
}


  行为是一些场景图结点。它们包含能够影响图中其它部分的代码,例如移动形状或改变灯光。它们可以用于监控图形,从而把细节信息传递到应用程序中的非3D部分。

  有关细节可能要比这个伪代码中所建议的更为复杂,例如,Java 3D使用多线程来执行并行遍历和着色。然而,了解一下这个过程的大致思想将有助于你理解本文后面的代码。

 三、 创建场景图

  这个场景图是通过WrapCheckers3D的构造器创建的:

public WrapCheckers3D( ){
 //初始化代码
 GraphicsConfiguration config =SimpleUniverse.getPreferredConfiguration( );
 Canvas3D canvas3D = new Canvas3D(config);
 add("Center", canvas3D);
 canvas3D.setFocusable(true); //聚焦画布
 canvas3D.requestFocus( );
 su = new SimpleUniverse(canvas3D);
 createSceneGraph( );
 initUserPosition( ); //设置用户的观察点
 orbitControls(canvas3D); //控制移动观察点
 su.addBranchGraph( sceneBG );
}


   该Canvas3D对象被从getPreferredConfiguration()中得到的配置初始化;这个方法查询有关硬件的着色信息。一些老式的 Java 3D程序并不初始化一个GraphicsConfiguration对象,它们使用null作为到Canvas3D构造器的参数。这是一种不好的编程方 法。

  聚焦于canvas3D会使得键盘输入事件被发送到场景图中的行为中。行为经常是通过键的按下与释放来激活的,但是它们也可以由 定时器、帧变化和由Java 3D内部生成的事件来触发。在Checkers3D中不存在任何行为,所以没有必要聚焦。我把这相应的几行代码保留下来,因为它们在我们后面将要讨论的几 乎每种其它程序中都要使用。

  SimpleUniverse对象创建一标准视图分支图和场景图的VirtualUniverse及 Locale结点。createSceneGraph()方法设置灯光、天空背景、地板及浮动的球体;initUserPosition()和 orbitControls()负责处理观察者问题。在该方法的最后,结果BranchGroup被添加到该场景图上:

private void createSceneGraph( ){
 sceneBG = new BranchGroup();
 bounds = new BoundingSphere(new Point3d(0,0,0), BOUNDSIZE);
 lightScene( ); //添加灯
 addBackground( ); //添加天空
 sceneBG.addChild( new CheckerFloor( ).getBG( ) );//添加地板
 floatingSphere( ); //添加浮动的球体
 sceneBG.compile( ); //修改场景
} //createSceneGraph()结束


   各种方法把子图添加到sceneBG上以构建内容分支图。一旦该图被终结化并允许Java 3D对它进行优化,sceneBG就被编译。这种优化包含生成图、重分组和组合结点。例如,一串包含不同平移的TransformGroup结点可能组合 到单个的结点中。另一种可能是把所有的形状用相同的外观属性分组,这样它们可以更快地着色。

  边界是一个全局的BoundingSphere,用来指定对于灯光、背景和OrbitBehavior对象等环境结点的影响。边界球体放置在场景的中央并影响BOUNDSIZE个单位半径内的一切。边界盒和边界多面体在Java 3D中都是可用的。

  在WrapCheckers3D( )执行最后的场景图显示在图3中。

  其中的"Floor Branch"结点是我的发明,用来隐藏一些细节直到最后。图3中没有显示的是场景图的视图分支部分。

  四、 点亮场景

  一个环境灯光和两个有向灯光被通过lightScene()方法添加到该场景上。一个环境灯光可以达到世界中的每个角落并同等程度地照亮一切。

Color3f white = new Color3f(1.0f, 1.0f, 1.0f);
//设置环境灯光
AmbientLight ambientLightNode = new AmbientLight(white);
ambientLightNode.setInfluencingBounds(bounds);
sceneBG.addChild(ambientLightNode);


  这里环境光源是沿着边界创建的并且被添加到场景中。Color3f()函数中使用了红/绿/蓝色,范围为0.0f~1.0f。

  有向灯光模仿了具有一定距离的灯光的效果,从一个特定方向来照亮物体的表面。其与环境光的主要区别在于,它需要一个有关的矢量。

Vector3f light1Direction=new Vector3f(-1.0f,-1.0f,-1.0f);
//左面,下面,后面
DirectionalLight light1 = new DirectionalLight(white, light1Direction);
light1.setInfluencingBounds(bounds);
sceneBG.addChild(light1);

图3.Checkers3D中的部分场景图


  方向是连接点(0,0,0)和点(-1,-1,-1)的矢量;而灯光可以被想象成是方向与该矢量一致的多条平等线。

  点光源和斑点光源是其它形式的Java 3D光。点光源位置在空间上,其方向朝各个方向发出。斑点光源是聚集的点光源,它指向一个特定方向。

  一个场景的背景可以用一个固定的颜色(如下)、一静态的图像或一个纹理贴图几何体例如一个球体来指定:

Background back = new Background( );
back.setApplicationBounds( bounds );
back.setColor(0.17f, 0.65f, 0.92f); //天空颜色
sceneBG.addChild( back );

五、 浮动的球体

  球体是一个工具类,来自于Java 3D的com.sun.j3d.utils.geometry包,这是一个Primitive类的子类,而Primitive类是一个Group结点-它 有一个Shape3D子结点(见图3)。它的几何体在一个Java 3D TriangleStripArray中相邻-它指定球体是一个相连接的三角形的数据。我不必调整这个几何体,但是该球体的外观和位置确实需要改变。

  Appearance结点是一个包含大量参考信息-包括色彩、线、点、多边形、着色、透明度和材质属性-的容器。

  ColouringAttributes修正一个形状的颜色并且不受场景灯光的影响。对于一个要求颜色和光相交互的形状来说,需要使用Material组件。要使光影响一个形状的颜色,必须满足三个条件:

  ·该形状的几何体必须包括法线。

  ·该形状的Appearance结点必须有一个Material组件。

  ·该Material组件必须用setLightingEnable()启动了灯光效果。

  工具球体类能自动地创建法线,因此第一个条件很容易满足。

  六、 给球体加上颜色

  Java 3D Material组件控制一个形状当被不同的灯光点亮时展示什么颜色:

Material mat = new Material(ambientColor, emissiveColor,
diffuseColor, specularColor, shininess);


   ambientColor参数指定当被环境光点亮时形状的颜色:这使得对象具有一个统一的颜色。emissiveColor代表形状产生的颜色;这个参 数经常被置为黑色(等于off)。diffuseColor是对象点亮时的颜色,其亮度依赖于光柱与形状的表面形成的角度的大小。

  提示: 散射和环境颜色常被设置为相同色,这与真实世界中的大多数物体被颜色点亮时的方式相匹配。

  specularColor参数与形状与它的发光区的反射程度相关。这个值与亮度参数结合在一起。

  提示: 镜面光的颜色常被设置为白色,这与真实世界中的由大多数物体生成的镜面光的颜色相匹配。

  在Checkers3D中,有两个有向光源-它们在浮动球体的顶部创建两个闪亮的光环(见图1)。地板瓦还没有点亮,因为它们的颜色是用形状的几何体来设置的(见后面)。

  在floatingSphere()中管理球体的外观的代码如下:

Color3f black = new Color3f(0.0f, 0.0f, 0.0f);
Color3f blue = new Color3f(0.3f, 0.3f, 0.8f);
Color3f specular = new Color3f(0.9f, 0.9f, 0.9f); //近乎白色
Material blueMat= new Material(blue, black, blue, specular, 25.0f);
blueMat.setLightingEnable(true);
Appearance blueApp = new Appearance( );
blueApp.setMaterial(blueMat);


  七、 放置球体

   放置一个形状几乎总是一直通过把它的场景图结点放到一个TransformGroup(见图3中的球体Group)的下方来实现的。可以用一个 TransformGroup来放置、旋转和缩放放在它下面的结点,这里变换是用Java 3D Transform3D对象来定义的:

Transform3D t3d = new Transform3D();
t3d.set( new Vector3f(0,4,0)); //放在(0,4,0)
TransformGroup tg = new TransformGroup(t3d);
tg.addChild(new Sphere(2.0f, blueApp));
//设置球体的半径和外观
//并缺省地设置其法线
sceneBG.addChild(tg);


  这个set()方法把球体的中心放在(0,4,0)并且重置任何以前的旋转或缩放。set()可以用来在重置其它变换的同时实现缩放和旋转。方法setTranslation(),setScale()和setRotation()仅影响给定的变换。

  不象其它一些3D绘图包,Java 3D中的y轴在垂直方向上,而地面是由XZ平面定义的,如图4所示。

  在Checkers3D中球体的位置被设置为(0,4,0),这把它的中心放置到XZ平面上方4个单位的位置。


图4.在Java 3D中的轴向

原文:http://soft.yesky.com/468/2347968.shtml



猜你喜欢

转载自dreamslink.iteye.com/blog/1182826