top
Loading...
用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://www.zhujiangroad.com
來源:http://www.zhujiangroad.com
北斗有巢氏 有巢氏北斗