服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

用java开发3d游戏之创建场景


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

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

用java开发3d游戏之创建场景(图一)
图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应用程序的所有的公共和私有数据及方法。

用java开发3d游戏之创建场景(图二)
图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);

用java开发3d游戏之创建场景(图三)
图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个单位的位置。

用java开发3d游戏之创建场景(图四)
图4.在java 3d中的轴向

扫描关注微信公众号