国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

Java移動設備D圖形:M3G快速模式(組圖)

2019-11-18 12:13:44
字體:
來源:轉載
供稿:網友

  本文是此系列兩部分中的第 1 部分,介紹了 Mobile 3D Graphics API (JSR 184) 的有關內容。作者將帶領您進入 java 移動設備的 3D 編程世界,并展示了處理光線、攝像機和材質的方法。
  
  在移動設備上玩游戲是一項有趣的消遣。迄今為止,硬件性能已足以滿足經典游戲概念的需求,這些游戲確實令人著迷,但圖像非常簡單。今天,人們開發出大量二維平面動作游戲,其圖像更為豐富,彌補了俄羅斯方塊和吃豆游戲的單調感。下一步就是邁進 3D 圖像的世界。Sony PlayStation Portable 將移動設備能夠實現的圖像能力展現在世人面前。雖然普通的移動電話在技術上遠不及這種特制的游戲機,但由此可以看出整個市場的發展方向。Mobile 3D Graphics API(簡稱為 M3G)是在 JSR 184(Java 規范請求,Java Specification Request)中定義的,JSR 184 是一項工業成就,用于為支持 Java 程序設計的移動設備提供標準 3D API。
  
  M3G API 大致可分為兩部分:快速模式和保留模式。在快速模式下,您渲染的是單獨的 3D 對象;而在保留模式下,您需要定義并顯示整個 3D 對象世界,包括其外觀信息在內。可以將快速模式視為低級的 3D 功能實現方式,保留模式顯示 3D 圖像的方式更為抽象,令人感覺也更要舒適一些。本文將對快速模式 API 進行介紹。而本系列的第 2 部分將介紹保留模式的使用方法。
  
  M3G 以外的技術
  
  M3G 不是孤獨的。HI Corporation 開發的 Mascot Capsule API 在日本國內非常流行,日本三大運營商均以不同形式選用了這項技術,在其他國家也廣受歡迎。例如,Sony EriCSSon 為手機增加了 M3G 和 HI Corporation 的特定 API。根據應用程序開發人員在 Sony Ericsson 網站上發布的報告,Mascot Capsule 是一種穩定且快速的 3D環境。
  
  JSR 239 也就是 Java Bindings for OpenGL ES,它面向的設備與 M3G 相同。OpenGL ES 是人們熟知的 OpenGL 3D 庫的子集,事實上已成為約束設備上本地 3D 實現的標準。JSR 239 定義了一個幾乎與 OpenGL ES 的 C 接口相同的 Java API,使現有 OpenGL 內容的移植更為輕易。到 2005 年 9 月為止,JSR 239 還依然處于早期的藍圖設計狀態。關于它是否會給手機帶來深刻的影響,我只能靠推測。盡管 OpenGL ES 與其 API 不兼容,但卻對 M3G 的定義產生了一定影響:JSR 184 專家組確保了 MSG 在 OpenGL ES 之上的有效實現。假如您了解 OpenGL,那么就會在 M3G 中看到許多似曾相識的屬性。
  
  盡管還有其他可選技術,但 M3G 獲得了所有主要電話制造商和運營商的支持。之前我提到過,游戲是最大的吸引力所在,但 M3G 是一種通用 API,您可以將其用于創建各種 3D 內容。未來的幾年中,手機將廣泛采用 3D API。
  
  您的第一個 3D 對象
  
  在第一個示例中,我們將創建一個如圖 1 所示的立方體。
  
  
圖 1. 示例立方體: a) 有頂點索引的正面圖,b) 切割面的側面視圖(正面,側面)
   Java移動設備D圖形:M3G快速模式(組圖)(圖一)

  這個立方體存在于 M3G 定義的右手坐標系中。舉起右手、伸出拇指、食指和中指,保持其中任一手指與其他兩指均成直角,那么拇指就表示 x 軸、食指表示 y 軸,中指表示 z 軸。試著將拇指和食指擺成圖 1a 中的樣子,那么您的中指必然指向自己。我在這里使用了 8 個頂點(立方體的頂點)并使立方體的中心與坐標系的原點相重合。
  
  從圖 1 中可以看到,拍攝 3D 場景的攝像機朝向 z 軸的負軸方向,正對立方體。攝像機的位置和屬性定義了隨后將在屏幕上顯示的東西。圖 1b 展示了同一場景的側面視圖,這樣您就可以更輕易地看清攝像機究竟能看到 3D 世界中的哪些地方。限制因素之一就是觀察角度,這與使用照相機的情況類似:長焦鏡頭的視野比廣角鏡頭的觀察角度要窄得多。因此觀察角度決定了您的視野。與真實世界中的情況不同,3D 計算給我們增加了兩個視圖邊界:近切割面和遠切割面。觀察角度和切割面共同定義了視域。視域中的一切都是可見的,而超出視域范圍的一切均不可見。
  
  在清單 1 中,您可以看到 VerticesSample 類,實現了上面提到的所有內容。
  
  清單 1. 顯示立方體的示例,第 1 部分:類成員
  
  package m3gsamples1;
  
  import javax.microedition.lcdui.*;
  import javax.microedition.m3g.*;
  
  /**
  * Sample displaying a cube defined by eight vertices, which are connected
  * by triangles.
  *
  * @author Claus Hoefele
  */
  public class VerticesSample extends Canvas implements Sample
  {
  /** The cube's vertex positions (x, y, z). */
  PRivate static final byte[] VERTEX_POSITIONS = {
  -1, -1, 1,  1, -1, 1,  -1, 1, 1,  1, 1, 1,
  -1, -1, -1,  1, -1, -1,  -1, 1, -1,  1, 1, -1
  };
  
  /** Indices that define how to connect the vertices to build
  * triangles. */
  private static int[] TRIANGLE_INDICES = {
  0, 1, 2, 3, 7, 1, 5, 4, 7, 6, 2, 4, 0, 1
  };
  
  /** The cube's vertex data. */
  private VertexBuffer _cubeVertexData;
  
  /** The cube's triangles defined as triangle strips. */
  private TriangleStripArray _cubeTriangles;
  
  /** Graphics singleton used for rendering. */
  private Graphics3D _graphics3d;
  
  VerticesSample 繼續自 Canvas,應該能夠直接繪制到屏幕。并且還實現了 Sample,定義它的目的是協助組織本文中的其他源代碼示例。VERTEX_POSITIONS 以同樣的順序定義了與圖 1a 相同的 8 個頂點。例如,頂點 0 定義為坐標(-1, -1, 1)。由于我將立方體的中心點放在坐標系原點位置處,因此立方體的各邊長應為 2 個單位。隨后,攝像機的位置和視角可定義一個單位在屏幕上所占的像素數。
  
  僅有頂點位置還不夠,您還必須描述出想要建立的幾何圖形。只能像逐點描圖法那樣,將頂點用直線連接起來,最終得到所需圖形。但 M3G 也帶來了一個約束:必須用三角形建立幾何圖形。任何多邊形都可定義為一組三角形的集合,因此三角形在 3D 實現中應用十分廣泛。三角形是基本的繪圖操作,在此基礎上可建立更為抽象的操作。
  
  不幸的是,假如只能使用三角形描述立方體,就需要 6 條邊 * 2 個三角形 * 3 個頂點 = 36 個頂點。這么多重復的頂點顯然浪費了大量內存。為節約內存,首先應將頂點與其三角形定義分隔開來。TRIANGLE_INDICES 使用 VERTEX_POSITIONS 數組索引定義幾何圖形,使頂點可重用。然后用三角形帶取代三角形,從而減少索引數量。通過使用三角形帶,新的三角形可重用最后兩個索引。舉例來說,三角形帶(0,1,2,3)可轉換為兩個三角形(0,1,2)及(1,2,3)。圖 1a 的各角均已標注相應索引數,假如您在圖 1a 的 TRIANGLE_INDICES 中遵循這一規則處理,就會發現兩個面之間意外地多出了一些三角形。這只是一種用于避免定義某些三角形帶的模式。我曾用一個有 14 個立方體索引的三角形帶處理過 8 個頂點的情況。
  
  使用其余的類成員即可繪制出立方體。清單 2 展示了其初始化方法。
  
  清單 2. 顯示立方體的示例,第 2 部分:初始化
  
  /**
  * Called when this sample is displayed.
  */
  public void showNotify()
  {
  init();
  }
  
  /**
  * Initializes the sample.
  */
  protected void init()
  {
  // Get the singleton for 3D rendering.
  _graphics3d = Graphics3D.getInstance();
  
  // Create vertex data.
  _cubeVertexData = new VertexBuffer();
  
  VertexArray verteXPositions =
  new VertexArray(VERTEX_POSITIONS.length/3, 3, 1);
  vertexPositions.set(0, VERTEX_POSITIONS.length/3, VERTEX_POSITIONS);
  _cubeVertexData.setPositions(vertexPositions, 1.0f, null);
  
  // Create the triangles that define the cube; the indices point to
  // vertices in VERTEX_POSITIONS.
  _cubeTriangles = new TriangleStripArray(TRIANGLE_INDICES,
  new int[] {TRIANGLE_INDICES.length});
  
  // Create a camera with perspective projection.
  Camera camera = new Camera();
  float aspect = (float) getWidth() / (float) getHeight();
  camera.setPerspective(30.0f, aspect, 1.0f, 1000.0f);
  Transform cameraTransform = new Transform();
  cameraTransform.postTranslate(0.0f, 0.0f, 10.0f);
  _graphics3d.setCamera(camera, cameraTransform);
  }
  
  init() 中的第一個步驟就是使用戶獲取圖形上下文(GC),以便繪制 3D 圖形。Graphics3D 是一個單元素,_graphics3d 中保存了一個引用,以備將來使用。接下來,創建一個 VertexBuffer 以保存頂點數據。在后文中可以看到,可以為一個頂點指派多種類型的信息,所有頂點都包含于 VertexBuffer 之中,在設置使用 _cubeVertexData.setPositions() 的 VertexArray 中,您惟一需要獲取的信息就是頂點位置。VertexArray 構造函數中保存了頂點數量(8 個)、各頂點的組件數(x, y, z)以及各組件的大小(1 字節)。由于這個立方體非常小,1 個字節足以容納一個坐標。假如需要創建大型的對象,那么還可以創建使用 Short 值(2 個字節)的 VertexArray。但不能使用實數,只能使用整數。接下來,使用 TRIANGLE_INDICES 中的索引對 TriangleStripArray 進行初始化操作。
  
  初始化代碼的最后一部分就是攝像機設置。在 setPersective() 中,可設置觀察角度、縱橫比和剪貼板。注重縱橫比和剪貼板的值應為浮點值。M3G 需要 Java 虛擬機(Java Virtual Machine,JVM)提供浮點值支持,這是在 CLDC 1.1 以后的版本中增加的功能。經過觀察后,將攝像機從立方體處移開,以查看對象的全視圖。可通過平移實現這一操作,轉換 部分將就這一主題進行具體討論。現在,您只要相信,帶有第三個正數參數的 postTranslate() 可使攝像機沿 z 軸移動。
  
  初始化后,您就可以將場景渲染到屏幕上。清單 3 實現了此功能。
  
  清單 3. 顯示立方體的示例,第 3 部分:繪圖
  
  /**
  * Renders the sample on the screen.
  *
  * @param graphics the graphics object to draw on.
  */
  protected void paint(Graphics graphics)
  {
  _graphics3d.bindTarget(graphics);
  _graphics3d.clear(null);
  _graphics3d.render(_cubeVertexData, _cubeTriangles,
  new Appearance(), null);
  _graphics3d.releaseTarget();
  }
  
  關于示例代碼
  
  假如想嘗試建立并運行本文中的示例,可以在 “下載” 部分中下載完整的源代碼。我使用了 Sun 的 Java Wireless Toolkit 2.2,并將我的項目配置為使用 MIDP 1.0、CLDC 1.1 —— 當然,還有 M3G。我將各部分的示例均作為單獨類加以實現。另外還實現了一個簡單的界面,您可以在這里選擇及執行各個示例。wi-m3gsamples1.zip 壓縮包中包含 readme.txt 文件,其中的信息更為具體。
  
  在 paint() 中,bindTarget() 將 Canvas 的圖形上下文指派給 Graphics3D。從而開始渲染 3D 對象,到調用 releaseTarget() 時終止渲染。調用 clear() 清除背景后,即可通過 init() 中創建的頂點數據和三角形繪制對象。許多 Graphics3D 的方法都會拋出不可控異常,但絕大多數錯誤都是不可恢復的,所以我決定不在代碼中使用 try/catch 塊。可在 VerticesSample.java 處找到本文的全部源代碼。
  
  我編寫了一個簡單的 MIDlet,用于顯示示例。可從 下載 中獲得 MIDlet 及本文全部源代碼。示例運行結果如圖 2 所示。
  
  
圖 2. 示例立方體
   Java移動設備D圖形:M3G快速模式(組圖)(圖二)

  很難看出這個屏幕上的矩形就是立方體,這是因為我將攝像機放置在其正對面,這就像站在一堵白墻前面一樣。為什么是白色呢?我還沒有指派任何顏色,而默認顏色就是白色。下一節將在顏色方面對程序進行完善。
  
  頂點顏色
  
  創建 VertexBuffer 時,我曾提到可以為一個頂點指派多種類型的信息 —— 顏色也是其中之一。圖形硬件是以流水線形式處理頂點的,就像工廠以流水線組裝汽車一樣。它逐個地對各頂點進行一系列的處理,直至各頂點都顯示在屏幕上。在這一架構中,來自所有頂點的所有數據都必須同時可用。可以設想,假如組裝工人必須每次從不同的地方取螺絲,效率該有多么低。
  
  圖 3 以平面布局展示了立方體的前五個頂點,還包括(R,G,B)格式的顏色信息。角上的數字同樣是在三角形帶中使用的索引。
  
  
圖 3. 帶有索引、頂點顏色和方位的三角形帶
   Java移動設備D圖形:M3G快速模式(組圖)(圖三)

  為頂點指派顏色對三角形內的像素會有什么影響呢?可能性之一就是為整個三角形使用同樣的頂點顏色。另外還有可能在兩個頂點之間插入顏色,實現顏色梯度效果。M3G 答應用戶在這兩個選項之間任選其一。在平面著色渲染模式下,可用三角形的第三個頂點顏色為整個三角形著色。假如您將圖 2 所示第 1 個三角形定義為(0,1,2),則其顏色為紅色(255,0,0)。在光影渲染模式下,三角形中的各像素都通過插值而獲得了自己的顏色。索引 0 和 2 之間的像素初始顏色為綠色(0,255,0),漸變為紅色。有些三角形共享索引 2 和索引 3 處的頂點,由于一個頂點只能有一種顏色,所以這也就意味著這些三角形也使用了一種相同的顏色。
  
  圖 3 還指出了索引的定義順序。例如,(0,1,2)按逆時針方向定義第 1 個三角形的頂點,而第二個三角形為(1,2,3)是按照順時針方向定義的。這就叫做多邊形環繞。可以利用它來確定哪個面在前,哪個面在后。從正前方查看立方體時,您總是會認為自己看的僅僅是外部,但假如盒子能打開呢?您一定也想到里邊去看看。立方體的每一面都有正反兩面。默認地,逆時針方向表示正面。
  
  但這里還有一個小小的問題:如圖 3 所示,三角形帶中的環繞在每個后續三角形處都會發生變化。按慣例,由三角形帶中的第一個三角形定義其環繞。當我將一個三角形帶環繞在清單 1 實現的整個立方體上時,首先從一個逆時針方向環繞的三角形(0,1,2)開始。通過這樣的方式,也就隱式地將立方體的外部定義為正面,而將內部作為背面。根據具體的需求,您可以要求 M3G 僅渲染正面、僅渲染背面或同時渲染兩面。假如立方體有一個半掩的蓋子,您同時可看到其正面和背面,此時同時渲染兩面的操作非常有用。假如可能,您應該禁用那些看不到的面,這樣可以提高渲染速度。將三角形排除在渲染操作之外的方法稱為背景揀出。
  
  清單 4 示范了使用頂點顏色的方法。
  
  清單 4. 各頂點都有顏色的立方體,第 1 部分:初始化頂點顏色
  
  /** The cube's vertex colors (R, G, B). */
  private static final byte[] VERTEX_COLORS = {
  0, (byte) 255, 0,       0, (byte) 255, (byte) 255,
  (byte) 255, 0, 0,       (byte) 255, 0, (byte) 255,
  (byte) 255, (byte) 255, 0,  (byte) 255, (byte) 255, (byte) 255,
  0, 0, (byte) 128,       0, 0, (byte) 255,
  };
  
  /**
  * Initializes the sample.
  */
  protected void init()
  {
  // Get the singleton for 3D rendering.
  _graphics3d = Graphics3D.getInstance();
  
  // Create vertex data.
  _cubeVertexData = new VertexBuffer();
  
  VertexArray vertexPositions =
  new VertexArray(VERTEX_POSITIONS.length/3, 3, 1);
  vertexPositions.set(0, VERTEX_POSITIONS.length/3, VERTEX_POSITIONS);
  _cubeVertexData.setPositions(vertexPositions, 1.0f, null);
  
  VertexArray vertexColors =
  new VertexArray(VERTEX_COLORS.length/3, 3, 1);
  vertexColors.set(0, VERTEX_COLORS.length/3, VERTEX_COLORS);
  _cubeVertexData.setColors(vertexColors);
  
  // Create the triangles that define the cube; the indices point to
  // vertices in VERTEX_POSITIONS.
  _cubeTriangles = new TriangleStripArray(TRIANGLE_INDICES,
  new int[] {TRIANGLE_INDICES.length});
  
  // Define an appearance object and set the polygon mode. The
  // default values are: SHADE_SMOOTH, CULL_BACK, and WINDING_CCW.
  _cubeAppearance = new Appearance();
  _polygonMode = new PolygonMode();
  _cubeAppearance.setPolygonMode(_polygonMode);
  
  // Create a camera with perspective projection.
  Camera camera = new Camera();
  float aspect = (float) getWidth() / (float) getHeight();
  camera.setPerspective(30.0f, aspect, 1.0f, 1000.0f);
  Transform cameraTransform = new Transform();
  cameraTransform.postTranslate(0.0f, 0.0f, 10.0f);
  _graphics3d.setCamera(camera, cameraTransform);
  }
  
  /**
  * Renders the sample on the screen.
  *
  * @param graphics the graphics object to draw on.
  */
  protected void paint(Graphics graphics)
  {
  _graphics3d.bindTarget(graphics);
  _graphics3d.clear(null);
  _graphics3d.render(_cubeVertexData, _cubeTriangles,
  _cubeAppearance, null);
  _graphics3d.releaseTarget();
  
  drawMenu(graphics);
  }
  
  在類成員部分的 VERTEX_COLORS 中定義了各頂點顏色。將顏色放在 init()中全新的 VertexArray 內,并通過調用 setColors() 將其指派給 VertexBuffer。在這段代碼中還初始化了一個名為 _cubeAppearance 的 Appearance 對象,_graphics3d.render() 使用該對象來更改立方體外觀。PolygonMode 是 _cubeAppearance 的一部分,其中包含更改多邊形級屬性(包括顯示哪些面)的方法。為交互地更改這些屬性,我還在代碼中增加了一個 keyPressed() 方法,如清單 5 所示。
  
  清單 5. 各頂點都有顏色的立方體,第 2 部分:處理按鍵事件
  
  /**
  * Handles key presses.
  *
  * @param keyCode key code.
  */
  protected void keyPressed(int keyCode)
  {
  switch (getGameAction(keyCode))
  {
  case FIRE:
  init();
  break;
  
  case GAME_A:
  if (_polygonMode.getShading() == PolygonMode.SHADE_FLAT)
  {
  _polygonMode.setShading(PolygonMode.SHADE_SMOOTH);
  }
  else
  {
  _polygonMode.setShading(PolygonMode.SHADE_FLAT);
  }
  break;
  
  case GAME_B:
  if (_polygonMode.getCulling() == PolygonMode.CULL_BACK)
  {
  _polygonMode.setCulling(PolygonMode.CULL_FRONT);
  }
  else
  {
  _polygonMode.setCulling(PolygonMode.CULL_BACK);
  }
  break;
  
  case GAME_C:
  if (_polygonMode.getWinding() == PolygonMode.WINDING_CCW)
  {
  _polygonMode.setWinding(PolygonMode.WINDING_CW);
  }
  else
  {
  _polygonMode.setWinding(PolygonMode.WINDING_CCW);
  }
  
  break;
  
  // no default
  }
  
  repaint();
  }
  
  鍵位映射
  
  示例中使用了 MIDP 的動作游戲作為處理按鍵事件的范例。其控制游戲動作的物理鍵映射到運行示例的設備上。Sun 的 Java Wireless Toolkit 將 LEFT、RIGHT、UP、DOWN 和 FIRE 映射為游戲操縱桿。GAME_A 映射為 1 鍵、GAME_B 映射為 3 鍵、GAME_C 映射為 7 鍵、GAME_D 映射為 9 鍵。
  
  按下相應的鍵更改以下三個屬性之一:渲染模式(平面著色渲染模式或光影渲染模式)、背景揀出(看見的是立方體的外面還是里面)、環繞(逆時針三角形表示的是正面還是背面)。圖 4 展示了這些選項。VertexColorssample.java 中包含該示例的完整源代碼。
  
  
圖 4. 經著色的立方體:a) 光影渲染模式;b) 平面著色渲染模式,背面被揀出;c) 正面被揀出,逆時針環繞
   Java移動設備D圖形:M3G快速模式(組圖)(圖四)
點擊查看大圖

  轉換
  
  在本文開始處,我曾經使用了一個 Transform 對象將攝像機向后移動,以便查看整個立方體。通過同樣的方式可以轉換任意 3D 對象。
  
  您可以通過數學方式將轉換表示為矩陣操作。一個向量 —— 例如,攝像機位置 —— 乘以恰當的平移矩陣從而得到相應移動的向量。Transform 對象就表示了這樣的一個矩陣。對于絕大多數普通轉換來說,M3G 提供了 3 種便于使用的接口,隱藏了底層的數學計算:
  
  Transform.postScale(float sx, float sy, float sz):在 x、y、z 方向伸縮 3D 對象。大于 1 的值將按照給定因數擴大對象;0 和 1 之間的值將縮小對象。負值則同時執行伸縮和鏡像操作。
  Transform.postTranslate(float tx, float ty, float tz):通過為 x、y 和 z 坐標增加指定值移動 3D 對象。負值則表示向負軸方向移動對象。
  Transform.postRotate(float angle, float ax, float ay, float az):按給定角度繞穿過(0, 0, 0)和(ax, ay, az)的軸旋轉對象。角度為正值,則表示若您順著正旋轉軸方向觀察,對象是按順時針旋轉的。例如,postRotate(30, 1, 0, 0) 將繞 x 軸將對象旋轉 30 度。
  所有操作名都是以 "post" 開頭的,表示當前 Transform 對象是從右邊與給定轉換矩陣相乘的 —— 矩陣操作的順序是非常重要的。假如您向右旋轉 90 度,然后走兩步,這時您所處的位置顯然與先走兩步再轉身不同。您可以在各步行指令之后調用兩個 post 方法 postRotate() 和 postTranslate(),從而獲得上面的步行指令。調用順序決定了所獲得的步行指令。由于使用的是后乘,所以您最后使用的轉換會首先應用。
  
  M3G 有一個 Transform 類和一個 Transformable 接口。所有快速模式的 API 均可接受 Transform 對象作為參數,用于修改其關聯的 3D 對象。另外,在保留模式下使用 Transformable 接口來轉換作為 3D 世界一部分的節點。在本系列的第 2 部分中將就此具體討論。
  
  清單 6 的示例展示了轉換。
  
  清單 6. 轉換
  
  /**
  * Renders the sample on the screen.
  *
  * @param graphics the graphics object to draw on.
  */
  protected void paint(Graphics graphics)
  {
  _graphics3d.bindTarget(graphics);
  _graphics3d.clear(null);
  _graphics3d.render(_cubeVertexData, _cubeTriangles,
  new Appearance(), _cubeTransform);
  _graphics3d.releaseTarget();
  
  drawMenu(graphics);
  }
  
  /**
  * Handles key presses.
  *
  * @param keyCode key code.
  */
  protected void keyPressed(int keyCode)
  {
  switch (getGameAction(keyCode))
  {
  case UP:
  transform(_transformation, TRANSFORMATION_X_AXIS, false);
  break;
  
  case DOWN:
  transform(_transformation, TRANSFORMATION_X_AXIS, true);
  break;
  
  case LEFT:
  transform(_transformation, TRANSFORMATION_Y_AXIS, false);
  break;
  
  case RIGHT:
  transform(_transformation, TRANSFORMATION_Y_AXIS, true);
  break;
  
  case GAME_A:
  transform(_transformation, TRANSFORMATION_Z_AXIS, false);
  break;
  
  case GAME_B:
  transform(_transformation, TRANSFORMATION_Z_AXIS, true);
  break;
  
  case FIRE:
  init();
  break;
  
  case GAME_C:
  _transformation++;
  _transformation %= 3;
  break;
  
  // no default
  }
  
  repaint();
  }
  
  /**
  * Transforms the cube with the given parameters.
  *
  * @param transformation transformation (rotate, translate, scale)
  * @param axis axis of translation (x, y, z)
  * @param positiveDirection true for increase, false for decreasing
  *             value.
  */
  protected void transform(int transformation, int axis,
  boolean positiveDirection)
  {
  if (transformation == TRANSFORMATION_ROTATE)
  {
  float amount = 10.0f * (positiveDirection ? 1 : -1);
  
  switch (axis)
  {
  case TRANSFORMATION_X_AXIS:
  _cubeTransform.postRotate(amount, 1.0f, 0.0f, 0.0f);
  break;
  
  case TRANSFORMATION_Y_AXIS:
  _cubeTransform.postRotate(amount, 0.0f, 1.0f, 0.0f);
  break;
  
  case TRANSFORMATION_Z_AXIS:
  _cubeTransform.postRotate(amount, 0.0f, 0.0f, 1.0f);
  break;
  
  // no default
  }
  }
  else if (transformation == TRANSFORMATION_SCALE)
  {
  float amount = positiveDirection ? 1.2f : 0.8f;
  
  switch (axis)
  {
  case TRANSFORMATION_X_AXIS:
  _cubeTransform.postScale(amount, 1.0f, 1.0f);
  break;
  
  case TRANSFORMATION_Y_AXIS:
  _cubeTransform.postScale(1.0f, amount, 1.0f);
  break;
  
  case TRANSFORMATION_Z_AXIS:
  _cubeTransform.postScale(1.0f, 1.0f, amount);
  break;
  
  // no default
  }
  }
  else if (transformation == TRANSFORMATION_TRANSLATE)
  {
  float amount = 0.2f * (positiveDirection ? 1 : -1);
  
  switch (axis)
  {
  case TRANSFORMATION_X_AXIS:
  _cubeTransform.postTranslate(amount, 0.0f, 0.0f);
  break;
  
  case TRANSFORMATION_Y_AXIS:
  _cubeTransform.postTranslate(0.0f, amount, 0.0f);
  break;
  
  case TRANSFORMATION_Z_AXIS:
  _cubeTransform.postTranslate(0.0f, 0.0f, amount);
  break;
  
  // no default
  }
  }
  }
  
  paint() 方法現有一個 Transform 對象 _cubeTransform,該對象是 _graphics3d.render() 調用的第 4 個參數。改進的 keyPressed() 方法中包含使用 transform() 交互地更改轉換的代碼。GAME_C 鍵在旋轉、平移和縮放立方體之間切換。UP/DOWN 鍵更改當前轉換的 x 軸,LEFT/RIGHT 更改 y 軸,GAME_A/GAME_B 更改 z 軸。按 FIRE 可將立方體重新設置為初始位置。您可以在 TransformationsSample.java 中找到完整的源代碼。
  
  
圖 5. 示例立方體:a) 旋轉;b) 平移;c) 縮放
   Java移動設備D圖形:M3G快速模式(組圖)(圖五)

  深度緩沖和投影
  
  這里我想介紹兩個在使用轉換時已用到但未說明過的概念:投影,定義了將 3D 對象映射到 2D 屏幕的方法;深度緩沖,是根據對象與攝像機之間的距離正確渲染對象的一種方法。
  
  要從攝像機的觀察點觀察渲染后的圖像,您必須考慮攝像機的位置和方位,將 3D 世界轉換為攝像機空間。在前面的示例代碼中,我用 Camera 和 Transform 對象調用了 Graphics3D.setCamera()。可將后者視為攝像機轉換或告訴 MSG 如何從世界坐標轉換為攝像機坐標的指令 —— 兩種定義都是正確的。最后,三維對象被顯示在二維屏幕上。到這里,Camera.setPerspective() 告訴了 M3G 在將 3D 轉換為 2D 空間時實現透視投影。
  
  透視投影與真實世界中的情況比較類似:當您俯視一條又長又直的道路時,道路兩邊看上去似乎在地平線處交匯了。距離攝像機越遠,路旁的對象看起來也就越小。您也可以忽略透視,以相同大小繪制所有對象,不管它們離得多遠。這對于某些應用程序,如 CAD 程序來說是很有意義的,因為沒有透視可更輕易地將精力集中在繪圖上。要禁用透視投影,可用 Camera.setParallel() 替換 Camera.setPerspective()。
  
  在攝像機空間中,對象的 z 坐標表示其與攝像機之間的距離。假如渲染一些具有不同 z 坐標的 3D 對象,那么您當然希望距離攝像機較近的對象比遠處的對象清楚。通過使用深度緩沖,對象可得到正確的渲染。深度緩沖與屏幕有著相同的寬和高,但用 z 坐標取代顏色值。它存儲著繪制在屏幕上的所有像素與攝像機之間的距離。然而,M3G 僅在一個像素比現有同一位置上的像素距離攝像機近時,才將其繪制出來。通過將進入的像素的 z 坐標與深度緩沖中的值相比較,就可以驗證這一點。因此,啟用深度緩沖可根據對象的 3D 位置渲染對象,而不受 Graphics3D.render() 命令順序的影響。反之,假如您禁用了深度緩沖,那么必須在繪制 3D 對象的順序上付出一定精力。在將目標圖像綁定到 Graphics3D 時,可啟用深度緩沖,也可不啟用。在使用接受一個參數的 bindTarget() 重載版本時,默認為啟用深度緩沖。在使用帶有三個參數的 bindTarget() 時,您可以通過作為第二個參數的布爾值顯式切換深度緩沖的開關狀態。
  
  您可以更改兩個屬性:深度緩沖與投影,如清單 7 所示:
  
  清單 7. 深度緩沖與投影
  
  /**
  * Initializes the sample.
  */
  protected void init()
  {
  // Get the singleton for 3D rendering.
  _graphics3d = Graphics3D.getInstance();
  
  // Create vertex data.
  _cubeVertexData = new VertexBuffer();
  
  VertexArray vertexPositions =
  new VertexArray(VERTEX_POSITIONS.length/3, 3, 1);
  vertexPositions.set(0, VERTEX_POSITIONS.length/3, VERTEX_POSITIONS);
  _cubeVertexData.setPositions(vertexPositions, 1.0f, null);
  
  // Create the triangles that define the cube; the indices point to
  // vertices in VERTEX_POSITIONS.
  _cubeTriangles = new TriangleStripArray(TRIANGLE_INDICES,
  new int[] {TRIANGLE_INDICES.length});
  
  // Create parallel and perspective cameras.
  _cameraPerspective = new Camera();
  
  float aspect = (float) getWidth() / (float) getHeight();
  _cameraPerspective.setPerspective(30.0f, aspect, 1.0f, 1000.0f);
  _cameraTransform = new Transform();
  _cameraTransform.postTranslate(0.0f, 0.0f, 10.0f);
  
  _cameraParallel = new Camera();
  _cameraParallel.setParallel(5.0f, aspect, 1.0f, 1000.0f);
  
  _graphics3d.setCamera(_cameraPerspective, _cameraTransform);
  _isPerspective = true;
  
  // Enable depth buffer.
  _isDepthBufferEnabled = true;
  }
  
  /**
  * Renders the sample on the screen.
  *
  * @param graphics the graphics object to draw on.
  */
  protected void paint(Graphics graphics)
  {
  // Create transformation objects for the cubes.
  Transform origin = new Transform();
  Transform behindOrigin = new Transform(origin);
  behindOrigin.postTranslate(-1.0f, 0.0f, -1.0f);
  Transform inFrontOfOrigin = new Transform(origin);
  inFrontOfOrigin.postTranslate(1.0f, 0.0f, 1.0f);
  
  // Disable or enable depth buffering when target is bound.
  _graphics3d.bindTarget(graphics, _isDepthBufferEnabled, 0);
  _graphics3d.clear(null);
  
  // Draw cubes front to back. If the depth buffer is enabled,
  // they will be drawn according to their z coordinate. Otherwise,
  // according to the order of rendering.
  _cubeVertexData.setDefaultColor(0x00FF0000);
  _graphics3d.render(_cubeVertexData, _cubeTriangles,
  new Appearance(), inFrontOfOrigin);
  _cubeVertexData.setDefaultColor(0x0000FF00);
  _graphics3d.render(_cubeVertexData, _cubeTriangles,
  new Appearance(), origin);
  _cubeVertexData.setDefaultColor(0x000000FF);
  _graphics3d.render(_cubeVertexData, _cubeTriangles,
  new Appearance(), behindOrigin);
  
  _graphics3d.releaseTarget();
  
  drawMenu(graphics);
  }
  
  /**
  * Handles key presses.
  *
  * @param keyCode key code.
  */
  protected void keyPressed(int keyCode)
  {
  switch (getGameAction(keyCode))
  {
  case GAME_A:
  _isPerspective = !_isPerspective;
  if (_isPerspective)
  {
  
  _graphics3d.setCamera(_cameraPerspective, _cameraTransform);
  }
  else
  {
  _graphics3d.setCamera(_cameraParallel, _cameraTransform);
  }
  break;
  
  case GAME_B:
  _isDepthBufferEnabled = !_isDepthBufferEnabled;
  break;
  
  case FIRE:
  init();
  break;
  
  // no default
  }
  
  repaint();
  }
  
  使用 GAME_A 鍵可在透視投影與平行投影之間切換。GAME_B 可啟用或禁用深度緩沖。完整的源代碼包含在 DepthBufferProjectionSample.java 中。圖 6 展示了不同設置下的效果。
  
  
圖 6. 立方體:a) 啟用深度緩沖,根據與攝像機之間的距離進行渲染;b) 禁用深度緩沖,根據繪圖操作的順序進行渲染;c) 使用平行投影而非透視投影進行渲染
   Java移動設備D圖形:M3G快速模式(組圖)(圖六)

  照明
  
  在一個沒有光線的房間中,所有的東西看上去都是黑的。那么前面的示例中沒有光線,怎么還能看到東西呢?頂點顏色和后面即將介紹的材質是不需要光線的,它們永遠顯示為定義好的顏色。但光線會使它們發生一些變化,可增加景深。
  
  光線的方向會根據對象的位置發生反射。假如您用手電筒垂直地照射您面前的鏡子,那么光線會反射到您身上。假如鏡子是傾斜的,則光線的入射角和反射角是完全相同的。總的來說,您需要一個與照射平面相垂直的方向向量。這一向量就稱為法線向量 或簡稱為法線。M3G 會根據法線、光源位置和攝像機位置計算著色情況。
  
  此外,法線是各頂點都具備的屬性,各頂點之間的像素著色既可采用插值法(PolygonMode.SHADE_SMOOTH)也可從三角形的第三個頂點處選取(PolygonMode.SHADE_FLAT)。由于立方體有 8 個頂點,支持法線的方法之一就是指定從立方體中心指向各角的向量,如圖 7a 所示。但這樣做可能會導致立方體著色不當。有三個面的顏色可能會相同,其中有些邊成為不可見狀態,使立方體看上去缺乏棱角。這顯然更適合球體,不太適合立方體。圖 7b 展示了如何為每邊使用 4 條法線 —— 共 24 條,從而創建棱角分明的邊線。由于一個頂點只能有一條法線,所以還要復制頂點。
  
  
圖 7.帶有法線向量的立方體:a) 8 條法線;b) 24 條法線(每邊 4 條)
   Java移動設備D圖形:M3G快速模式(組圖)(圖七)

  可使用法線計算光線后,還需要告訴 M3G 您需要什么類型的光線。光線來源于不同形式:燈泡、太陽、手電筒等等。在 M3G 中的對應術語分別為全向光、定向光和聚光。
  
  全向光是從一個點發出的,并平均地照射各個方向。沒有燈罩的燈泡發出的就是這樣的光。
  
  定向光向一個方向發出平行的光線。太陽離我們的距離非常遠,所以可以將其光線視為平行的。定向光沒有位置,只有方向。
  
  手電筒或劇場中使用的聚光燈發射出的光線就是聚光。其光線呈錐形,與圓錐相交的平面上的對象會被照亮。
  
  在真實世界中,光線還會從對象上反射回來而將四周照亮。假如您打開臥室燈,就會發現即便沒有能直接照射到床底下的光線,但床下仍會被照亮。Raytracer 通過追蹤從攝像機到光源的路徑而清楚真實地展示了圖像,但需要很長時間。要獲得交互式幀頻,必須滿足一個簡單的模型:環境光。環境光以不變的頻率從各方向照亮對象。您可以用環境光模擬前面的臥室場景,將所有對象都照亮到一定程度,從而提供了另外一個全向光源。
  
  清單 8 描述了設置不同光線的方法。
  
  清單 8. 設置光線模式
  
  // Create light.
  _light = new Light();
  _lightMode = LIGHT_OMNI;
  setLightMode(_light, _lightMode);
  Transform lightTransform = new Transform();
  lightTransform.postTranslate(0.0f, 0.0f, 3.0f);
  _graphics3d.resetLights();
  _graphics3d.addLight(_light, lightTransform);
  
  /**
  * Sets the light mode.
  *
  * @param light light to be modified.
  * @param mode light mode.
  */
  protected void setLightMode(Light light, int mode)
  {
  switch (mode)
  {
  case LIGHT_AMBIENT:
  light.setMode(Light.AMBIENT);
  light.setIntensity(2.0f);
  break;
  
  case LIGHT_DIRECTIONAL:
  light.setMode(Light.DIRECTIONAL);
  light.setIntensity(1.0f);
  break;
  
  case LIGHT_OMNI:
  light.setMode(Light.OMNI);
  light.setIntensity(2.0f);
  break;
  
  case LIGHT_SPOT:
  light.setMode(Light.SPOT);
  light.setSpotAngle(20.0f);
  light.setIntensity(2.0f);
  break;
  
  // no default
  }
  }
  
  在圖 8 中,您可以看到各種光線模式的不同效果。分別以 4 種類型的光照射示例立方體。這里的光線均為白色,就在攝像機前面,朝向立方體的三個面。
  
  
圖 8. 使用不同的光線照射立方體 a) 全向光;b) 聚光;c) 環境光;d) 定向光
   Java移動設備D圖形:M3G快速模式(組圖)(圖八)
點擊查看大圖

  全向光在面對光源的頂點處最亮,然后逐漸暗淡下來。另外,聚光在聚光圓錐的邊緣處制造了強烈的明暗對比。假如定義了一個足夠大的光錐,那么所得到的結果可能與全向光相同。環境光從各個方向照亮立方體,立方體看上去是平的,這是因為缺乏陰影。最后,定向光使每面都具有不同的顏色。每面內的顏色都相同,這是因為光線是平行的。
  
  照明并不精確,否則,聚光照亮的圓錐體范圍應該是圓形。這是因為光線計算比較復雜,手機的部件將簡化這一計算。可以為立方體的各邊添加更多的三角形,從而提高其顯示質量。盡管三角形并不能定義一個可見的幾何圖形,但可使 M3G 擁有更多的控制點(要計算的數量也更多)。
  
  材質
  
  通過光線可實現不同的效果。一個閃閃發光的銀色球反射光線的方式與一張紙顯然不同。M3G 使用以下屬性為這些材質的特征建立模型:
  
  環境反射:由環境光源反射的光線。
  漫反射:反射光均勻地分散到各個方向。
  放射光:一個像熾熱的物體那樣發射光線的對象。
  鏡面反射:光線從有光亮平面的對象反射回來。
  您可為各材質屬性設置顏色。閃閃發光的銀色球的漫反射光線應該是銀色的,其鏡面反射部分為白色。材質的顏色與光線的顏色相融合,從而得到最終的對象顏色。假如您用藍光照射銀色球,那么球看上去應該略帶藍色。
  
  清單 9 展示了使用材質的方式:
  
  清單 9. 設置材質
  
  // Create appearance and the material.
  _cubeAppearance = new Appearance();
  _colorTarget = COLOR_DEFAULT;
  setMaterial(_cubeAppearance, _colorTarget);
  
  /**
  * Sets the material according to the given target.
  *
  * @param appearance appearance to be modified.
  * @param colorTarget target color.
  */
  protected void setMaterial(Appearance appearance, int colorTarget)
  {
  Material material = new Material();
  
  switch (colorTarget)
  {
  case COLOR_DEFAULT:
  break;
  
  case COLOR_AMBIENT:
  material.setColor(Material.AMBIENT, 0x00FF0000);
  break;
  
  case COLOR_DIFFUSE:
  material.setColor(Material.DIFFUSE, 0x00FF0000);
  break;
  
  case COLOR_EMISSIVE:
  material.setColor(Material.EMISSIVE, 0x00FF0000);
  break;
  
  case COLOR_SPECULAR:
  
  material.setColor(Material.SPECULAR, 0x00FF0000);
  material.setShininess(2);
  break;
  
  // no default
  }
  
  appearance.setMaterial(material);
  }
  
  setMaterial() 創建了一個新的 Material 對象,通過使用各顏色組件標識符的 setColor() 設置顏色。Material 對象隨后被指派給 Appearance 對象,該對象用于調用 Graphics3D.render()。盡管這里沒有展示,但您還可以使用 Material.setVertexColorTrackingEnable() 為環境反射和漫反射使用頂點顏色,不必使用 Material.setColor()。LightingMaterialsSample.java 這一示例中實現了光線和材質。按其中的鍵可以將不同的顏色與材質綜合,感受不同的效果。
  
  在圖 9 中,用全向光展示了不同的材質特征。各截圖都將顏色組件設置為紅色,以突出表現其效果。
  
  
圖 9. 不同的顏色組件:a) 環境反射;b) 漫反射;c) 放射光;d) 鏡面反射
   Java移動設備D圖形:M3G快速模式(組圖)(圖九)
點擊查看大圖

  環境反射僅對環境光起作用,因此,使用全向光是無效的。漫反射材質組件會造成一種不光滑的表面,而放射光組件則制造出一種發光效果。鏡面反射顏色組件強調了發亮的效果。此外,您還可以通過使用更多的三角形改進明暗對比的著色質量。
  
  紋理
  
  至此,我已經介紹了更改立方體外觀的兩種方式:頂點顏色和材質。但經過這兩種方式處理后的立方體看起來依然很不真實。在現實世界中,應該還有更多的細節。這就是紋理的效果。紋理是像包在禮物外面的包裝紙那樣環繞在 3D 對象外的圖像。您必須為各種情況選擇恰當的包裝紙,并且決定如何排列。在 3D 編程中也必須作出相同的決策。
  
  現在,您或許已經猜測到我將引入另外一種每個頂點都具備的屬性。對于每個頂點而言,紋理坐標定義了使用紋理的位置。然后 M3G 會映射紋理以適應您的對象。可以這樣設想,將一塊有彈性的包裝紙釘在禮物的各頂點上。這些坐標所引用的紋理像素就叫做 texel。圖 10 展示了將 128 x 128 texel 的正方形紋理映射到立方體正面的效果。
  
  
圖 10:將多邊形坐標(x,y)映射為紋理坐標(s,t)
   Java移動設備D圖形:M3G快速模式(組圖)(圖十)

  將紋理坐標命名為(s,t)是為了與用于表示頂點位置的(x,y)區分開來(從文字角度來講,(u,v)更為常用)。坐標(s,t)定義為(0,0)的地方就是紋理的左上角,而(1,1)位于右下角。相應地,假如您需要將立方體正面的左下角映射到紋理的左下角,必須將紋理坐標(0,1)指派給頂點 0。
  
  由于您定義了與紋理的角相關的紋理坐標,所以任意大小的圖像都有相同的坐標。M3G 為最接近的 texel 插入 0 到 1 之間的值,例如,0.5 表示紋理的中點。假如紋理坐標超過 0~1 的范圍,M3G 會提示您確認。坐標既可環繞(例如,1.5 與 0.5 的效果相同),也可采用加強方式,所謂加強,也就意味著任何小于 0 的值都按 0 使用,任何大于 1 的值都按 1 使用。紋理的寬和高可有所不同,但必須是 2 的冪,如圖 1 中的 128。部件必須至少支持 256 的紋理大小,這是 M3G 的一個可選屬性。
  
  Graphics3D.getProperties() 返回一個 Hashtable,其中填充了特定于部件的屬性,如最大紋理維度或支持的最大光源數。getProperties() 的文檔包含一個屬性及其最低需求的清單。在使用超過這些值的屬性之前,應該檢查設備的部件是否能提供支持。
  
  清單 10 展示了紋理的使用。
  
  清單 10. 使用紋理,第 1 部分:初始化
  
  /** The cube's vertex positions (x, y, z). */
  private static final byte[] VERTEX_POSITIONS = {
  -1, -1, 1,  1, -1, 1,  -1, 1, 1,  1, 1, 1, // front
  1, -1, -1,  -1, -1, -1,  1, 1, -1,  -1, 1, -1, // back
  1, -1, 1,  1, -1, -1,  1, 1, 1,  1, 1, -1, // right
  -1, -1, -1,  -1, -1, 1,  -1, 1, -1,  -1, 1, 1, // left
  
  -1, 1, 1,  1, 1, 1,  -1, 1, -1,  1, 1, -1, // top
  -1, -1, -1,  1, -1, -1,  -1, -1, 1,  1, -1, 1 // bottom
  };
  
  /** Indices that define how to connect the vertices to build
  * triangles. */
  private static final int[] TRIANGLE_INDICES = {
  0, 1, 2, 3,  // front
  4, 5, 6, 7,  // back
  8, 9, 10, 11,  // right
  12, 13, 14, 15,  // left
  16, 17, 18, 19,  // top
  20, 21, 22, 23,  // bottom
  };
  
  /** Lengths of triangle strips in TRIANGLE_INDICES. */
  private static int[] TRIANGLE_LENGTHS = {
  4, 4, 4, 4, 4, 4
  };
  
  /** File name of the texture. */
  private static final String TEXTURE_FILE = "/texture.png";
  
  /** The texture coordinates (s, t) that define how to map the
  * texture to the cube. */
  private static final byte[] VERTEX_TEXTURE_COORDINATES = {
  0, 1,  1, 1,  0, 0,  1, 0,  // front
  0, 1,  1, 1,  0, 0,  1, 0,  // back
  0, 1,  1, 1,  0, 0,  1, 0,  // right
  0, 1,  1, 1,  0, 0,  1, 0,  // left
  0, 1,  1, 1,  0, 0,  1, 0,  // top
  0, 1,  1, 1,  0, 0,  1, 0,  // bottom
  };
  
  /** First color for blending. */
  private static final int COLOR_0 = 0x000000FF;
  
  /** Second color for blending. */
  private static final int COLOR_1 = 0x0000FF00;
  
  /**
  * Initializes the sample.
  */
  protected void init()
  {
  // Get the singleton for 3D rendering.
  _graphics3d = Graphics3D.getInstance();
  
  // Create vertex data.
  _cubeVertexData = new VertexBuffer();
  
  VertexArray vertexPositions =
  new VertexArray(VERTEX_POSITIONS.length/3, 3, 1);
  vertexPositions.set(0, VERTEX_POSITIONS.length/3, VERTEX_POSITIONS);
  _cubeVertexData.setPositions(vertexPositions, 1.0f, null);
  
  VertexArray vertexTextureCoordinates =
  
  new VertexArray(VERTEX_TEXTURE_COORDINATES.length/2, 2, 1);
  vertexTextureCoordinates.set(0,
  VERTEX_TEXTURE_COORDINATES.length/2, VERTEX_TEXTURE_COORDINATES);
  _cubeVertexData.setTexCoords(0, vertexTextureCoordinates, 2.0f, null);
  
  // Set default color for cube.
  _cubeVertexData.setDefaultColor(COLOR_0);
  
  // Create the triangles that define the cube; the indices point to
  // vertices in VERTEX_POSITIONS.
  _cubeTriangles = new TriangleStripArray(TRIANGLE_INDICES,
  TRIANGLE_LENGTHS);
  
  // Create a camera with perspective projection.
  Camera camera = new Camera();
  float aspect = (float) getWidth() / (float) getHeight();
  camera.setPerspective(30.0f, aspect, 1.0f, 1000.0f);
  Transform cameraTransform = new Transform();
  cameraTransform.postTranslate(0.0f, 0.0f, 10.0f);
  _graphics3d.setCamera(camera, cameraTransform);
  
  // Rotate the cube so we can see three sides.
  _cubeTransform = new Transform();
  _cubeTransform.postRotate(20.0f, 1.0f, 0.0f, 0.0f);
  _cubeTransform.postRotate(45.0f, 0.0f, 1.0f, 0.0f);
  
  // Define an appearance object and set the polygon mode.
  _cubeAppearance = new Appearance();
  _polygonMode = new PolygonMode();
  _isPerspectiveCorrectionEnabled = false;
  _cubeAppearance.setPolygonMode(_polygonMode);
  
  try
  {
  // Load image for texture and assign it to the appearance. The
  // default values are: WRAP_REPEAT, FILTER_BASE_LEVEL/
  // FILTER_NEAREST, and FUNC_MODULATE.
  Image2D image2D = (Image2D) Loader.load(TEXTURE_FILE)[0];
  _cubeTexture = new Texture2D(image2D);
  _cubeTexture.setBlending(Texture2D.FUNC_DECAL);
  
  // Index 0 is used because we have only one texture.
  _cubeAppearance.setTexture(0, _cubeTexture);
  }
  catch (Exception e)
  {
  System.out.println("Error loading image " + TEXTURE_FILE);
  e.printStackTrace();
  }
  }
  
  在 init() 中,向 VertexBuffer 增加了定義為類的靜態成員的紋理坐標。與在照明示例中的情況類似,我為立方體的每個面都使用了 4 個向量,以將各頂點映射到紋理的一個角。注重,我使用了比例 2.0 作為 _cubeVertexData.setTexCoords() 的第三個參數。這也就告訴 M3G 將所有紋理坐標都乘以此值。實際上,紋理僅使用了立方體面的四分之一。這樣做的目的是展示 M3G 的加強和環繞特性。假如是加強,那么僅在左上角繪制;假如是環繞,那么紋理圖案將填布滿整個面。
  
  紋理是用 Loader.load() 載入的,并指派給 Texture2D 對象。您還應使用 MIDP 的 Image.createImage(),但假如您想從 Java Archive(JAR)文件中讀取紋理,那么 Loader 類是最快的方式。所得到的 Texture2D 對象隨后被設置為立方體外觀的紋理。
  
  在進行紋理化處理時,您可能依然希望使用通過照明獲得或直接指派給頂點的顏色。出于此方面的考慮,M3G 提供了各種混色功能,可通過調用 _cubeTexture.setBlending() 來設置。在 init() 中,我使用了 Texture2D.FUNC_DECAL,它將根據 α 值將紋理與基本頂點顏色相混合。圖 10 中紋理圖像的灰色位透明度為 60%。這里沒有設置頂點顏色或使用照明,而是使用 _cubeVertexData.setDefaultColor() 為立方體設置了一個默認顏色,這也就意味著立方體中的所有三角形都將使用同樣的顏色。通過混色,您也可以在各紋理上使用多重紋理,從而獲得更豐富的效果。
  
  我還內置了一個可選的 M3G 特性。如照明部分中所示,渲染的質量取決于您所使用的三角形數量 —— 頂點之間的距離越小,插值效果就越好。這對于紋理來說也是成立的。高質量對于紋理而言就意味著紋理在不失真的情況下映射。如圖 10 所示的紋理是有缺陷的,因為其中包含直線,顯然會發生失真的情況。MSG 提供了一種在處理能力方面代價低廉的方法來解決這一問題。可用 PolygonMode.setPerspectiveCorrectionEnable() 設置可選的透視修正標志,如清單 11 所示。
  
  清單 11. 使用紋理,第 2 部分:交互式更改透視修正、環繞模式及混色
  
  /**
  * Checks whether perspective correction is supported.
  *
  * @return true if perspective correction is supported, false otherwise.
  */
  protected boolean isPerspectiveCorrectionSupported()
  {
  Hashtable properties = Graphics3D.getProperties();
  Boolean supportPerspectiveCorrection =
  (Boolean) properties.get("supportPerspectiveCorrection");
  
  return supportPerspectiveCorrection.booleanValue();
  }
  
  /**
  * Handles key presses.
  *
  * @param keyCode key code.
  */
  protected void keyPressed(int keyCode)
  {
  switch (getGameAction(keyCode))
  {
  case LEFT:
  _cubeTransform.postRotate(-10.0f, 0.0f, 1.0f, 0.0f);
  break;
  
  case RIGHT:
  _cubeTransform.postRotate(10.0f, 0.0f, 1.0f, 0.0f);
  break;
  
  case FIRE:
  init();
  break;
  
  case GAME_A:
  if (isPerspectiveCorrectionSupported())
  {
  _isPerspectiveCorrectionEnabled = !_isPerspectiveCorrectionEnabled;
  _polygonMode.setPerspectiveCorrectionEnable(
  _isPerspectiveCorrectionEnabled);
  }
  break;
  
  case GAME_B:
  if (_cubeTexture.getWrappingS() == Texture2D.WRAP_CLAMP)
  {
  _cubeTexture.setWrapping(Texture2D.WRAP_REPEAT,
  Texture2D.WRAP_REPEAT);
  }
  else
  {
  _cubeTexture.setWrapping(Texture2D.WRAP_CLAMP,
  Texture2D.WRAP_CLAMP);
  }
  break;
  
  case GAME_C:
  if (_cubeVertexData.getDefaultColor() == COLOR_0)
  {
  _cubeVertexData.setDefaultColor(COLOR_1);
  }
  else
  {
  _cubeVertexData.setDefaultColor(COLOR_0);
  }
  break;
  
  // no default
  }
  
  repaint();
  }
  
  在示例中,isPerspectiveCorrectionSupported() 用于檢查部件是否支持透視修正。假如支持,您可在 keyPressed() 中交互地切換標志的開關狀態。這里還增加了一個更改紋理映射到立方體的方式(加強或重復)的選項以及一個更改混色的選項。對混色的更改示范了可以輕易地將顏色與紋理相混合以獲得更豐富的效果。在 TexturesSample.java 中可以看到完整的示例。
  
  圖 11 展示了使用不同選項的紋理映射效果。
  
  
圖 11. 紋理:a) 無透視修正;b) 有透視修正;c) 加強而非平鋪;d) 用綠色代替蘭色進行混色
   Java移動設備D圖形:M3G快速模式(組圖)(圖十)
點擊查看大圖

  結束語
  
  本文中介紹了大量基礎知識,包括使用頂點數據創建立方體、使用攝像機為立方體照相、在立方體上應用光線和材質、利用紋理創建具有真實感的立方體的方法等具體信息。給出了許多立方體作為示例。
  
  在論證概念時,立方體是一種極好的示例,但它并不是復雜的 3D 設計的里程碑。在介紹過程中,我從游戲的角度強調了 3D 圖像。假如像示例那樣通過手工組合頂點數據,那么設計一個復雜的游戲世界將成為一項令人望而卻步的工作。您需要一種方法,通過建模工具來設計 3D 場景,并將數據導入程序。
  
  導入模型后,必須再尋求一種組織數據的方法。假如使用 VertexBuffer 方法,您必須記住所有的轉換以及對象之間的關系。比如說,上臂與下臂相連,而下臂應該與手相連。您必須對應地安置手臂與手。M3G 提供的一種場景圖形 API —— 保留模式 —— 簡化了此類任務,通過保留模式您可以為全部對象及其屬性建模。在本系列的第 2 部分中將就此進行具體論述。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 宝山区| 蒲江县| 旺苍县| 丰顺县| 武义县| 会宁县| 阿拉善右旗| 通海县| 海宁市| 沾化县| 宝兴县| 江都市| 邹平县| 平泉县| 林甸县| 大名县| 商南县| 治县。| 讷河市| 甘谷县| 青田县| 弥渡县| 新昌县| 宜都市| 吉林省| 耒阳市| 永昌县| 西贡区| 淮安市| 萨嘎县| 东城区| 阳高县| 开鲁县| 石楼县| 华亭县| 淅川县| 西安市| 江源县| 河北省| 绥棱县| 三穗县|