熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

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

2013-11-23 18:59:32  來源: Java核心技術 

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

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

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

  為頂點指派顏色對三角形內的像素會有什麼影響呢?可能性之一就是為整個三角形使用同樣的頂點顏色另外還有可能在兩個頂點之間插入顏色實現顏色梯度效果MG 允許用戶在這兩個選項之間任選其一在平面著色渲染模式下可用三角形的第三個頂點顏色為整個三角形著色如果您將圖 所示第 個三角形定義為(則其顏色為紅色(在光影渲染模式下三角形中的各像素都通過插值而獲得了自己的顏色索引 之間的像素初始顏色為綠色(漸變為紅色有些三角形共享索引 和索引 處的頂點由於一個頂點只能有一種顏色所以這也就意味著這些三角形也使用了一種相同的顏色
  
  圖 還指出了索引的定義順序例如)按逆時針方向定義第 個三角形的頂點而第二個三角形為()是按照順時針方向定義的這就叫做多邊形環繞可以利用它來確定哪個面在前哪個面在後從正前方查看立方體時您總是會認為自己看的僅僅是外部但如果盒子能打開呢?您一定也想到裡邊去看看立方體的每一面都有正反兩面默認地逆時針方向表示正面
  
  但這裡還有一個小小的問題如圖 所示三角形帶中的環繞在每個後續三角形處都會發生變化按慣例由三角形帶中的第一個三角形定義其環繞當我將一個三角形帶環繞在清單 實現的整個立方體上時首先從一個逆時針方向環繞的三角形()開始通過這樣的方式也就隱式地將立方體的外部定義為正面而將內部作為背面根據具體的需求您可以要求 MG 僅渲染正面僅渲染背面或同時渲染兩面如果立方體有一個半掩的蓋子您同時可看到其正面和背面此時同時渲染兩面的操作非常有用如果可能您應該禁用那些看不到的面這樣可以提高渲染速度將三角形排除在渲染操作之外的方法稱為背景揀出
  
  清單 示范了使用頂點顏色的方法
  
  清單 各頂點都有顏色的立方體 部分初始化頂點顏色
  
  /** The cubes vertex colors (R G B) */
  private static final byte[] VERTEX_COLORS = {
   (byte)        (byte) (byte)
  (byte)        (byte) (byte)
  (byte) (byte)   (byte) (byte) (byte)
   (byte)        (byte)
  };
  
  /**
  * Initializes the sample
  */
  protected void init()
  {
  // Get the singleton for D rendering
  _graphicsd = GraphicsDgetInstance();
  
  // Create vertex data
  _cubeVertexData = new VertexBuffer();
  
  VertexArray vertexPositions =
  new VertexArray(VERTEX_POSITIONSlength/ );
  vertexPositionsset( VERTEX_POSITIONSlength/ VERTEX_POSITIONS);
  _cubeVertexDatasetPositions(vertexPositions f null);
  
  VertexArray vertexColors =
  new VertexArray(VERTEX_COLORSlength/ );
  vertexColorsset( VERTEX_COLORSlength/ VERTEX_COLORS);
  _cubeVertexDatasetColors(vertexColors);
  
  // Create the triangles that define the cube; the indices point to
  // vertices in VERTEX_POSITIONS
  _cubeTriangles = new TriangleStripArray(TRIANGLE_INDICES
  new int[] {TRIANGLE_INDICESlength});
  
  // 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();
  _cubeAppearancesetPolygonMode(_polygonMode);
  
  // Create a camera with perspective projection
  Camera camera = new Camera();
  float aspect = (float) getWidth() / (float) getHeight();
  camerasetPerspective(f aspect f f);
  Transform cameraTransform = new Transform();
  cameraTransformpostTranslate(f f f);
  _graphicsdsetCamera(camera cameraTransform);
  }
  
  /**
  * Renders the sample on the screen
  *
  * @param graphics the graphics object to draw on
  */
  protected void paint(Graphics graphics)
  {
  _graphicsdbindTarget(graphics);
  _graphicsdclear(null);
  _graphicsdrender(_cubeVertexData _cubeTriangles
  _cubeAppearance null);
  _graphicsdreleaseTarget();
  
  drawMenu(graphics);
  }
  
  在類成員部分的 VERTEX_COLORS 中定義了各頂點顏色將顏色放在 init()中全新的 VertexArray 內並通過調用 setColors() 將其指派給 VertexBuffer在這段代碼中還初始化了一個名為 _cubeAppearance 的 Appearance 對象_graphicsdrender() 使用該對象來更改立方體外觀PolygonMode 是 _cubeAppearance 的一部分其中包含更改多邊形級屬性(包括顯示哪些面)的方法為交互地更改這些屬性我還在代碼中增加了一個 keyPressed() 方法如清單 所示
  
  清單 各頂點都有顏色的立方體 部分處理按鍵事件
  
  /**
  * Handles key presses
  *
  * @param keyCode key code
  */
  protected void keyPressed(int keyCode)
  {
  switch (getGameAction(keyCode))
  {
  case FIRE:
  init();
  break;
  
  case GAME_A:
  if (_polygonModegetShading() == PolygonModeSHADE_FLAT)
  {
  _polygonModesetShading(PolygonModeSHADE_SMOOTH);
  }
  else
  {
  _polygonModesetShading(PolygonModeSHADE_FLAT);
  }
  break;
  
  case GAME_B:
  if (_polygonModegetCulling() == PolygonModeCULL_BACK)
  {
  _polygonModesetCulling(PolygonModeCULL_FRONT);
  }
  else
  {
  _polygonModesetCulling(PolygonModeCULL_BACK);
  }
  break;
  
  case GAME_C:
  if (_polygonModegetWinding() == PolygonModeWINDING_CCW)
  {
  _polygonModesetWinding(PolygonModeWINDING_CW);
  }
  else
  {
  _polygonModesetWinding(PolygonModeWINDING_CCW);
  }
  
  break;
  
  // no default
  }
  
  repaint();
  }
  
  鍵位映射
  
  示例中使用了 MIDP 的動作游戲作為處理按鍵事件的范例其控制游戲動作的物理鍵映射到運行示例的設備上Sun 的 Java Wireless Toolkit 將 LEFTRIGHTUPDOWN 和 FIRE 映射為游戲操縱桿GAME_A 映射為 GAME_B 映射為 GAME_C 映射為 GAME_D 映射為
  
  按下相應的鍵更改以下三個屬性之一渲染模式(平面著色渲染模式或光影渲染模式)背景揀出(看見的是立方體的外面還是裡面)環繞(逆時針三角形表示的是正面還是背面) 展示了這些選項VertexColorsSamplejava 中包含該示例的完整源代碼
  
  
經著色的立方體a) 光影渲染模式b) 平面著色渲染模式背面被揀出c) 正面被揀出逆時針環繞
   

  轉換
  
  在本文開始處我曾經使用了一個 Transform 對象將攝像機向後移動以便查看整個立方體通過同樣的方式可以轉換任意 D 對象
  
  您可以通過數學方式將轉換表示為矩陣操作一個向量 —— 例如攝像機位置 —— 乘以恰當的平移矩陣從而得到相應移動的向量Transform 對象就表示了這樣的一個矩陣對於絕大多數普通轉換來說MG 提供了 種便於使用的接口隱藏了底層的數學計算
  
  TransformpostScale(float sx float sy float sz)在 xyz 方向伸縮 D 對象大於 的值將按照給定因數擴大對象 之間的值將縮小對象負值則同時執行伸縮和鏡像操作
  TransformpostTranslate(float tx float ty float tz)通過為 xy 和 z 坐標增加指定值移動 D 對象負值則表示向負軸方向移動對象
  TransformpostRotate(float angle float ax float ay float az)按給定角度繞穿過( )和(ax ay az)的軸旋轉對象角度為正值則表示若您順著正旋轉軸方向觀察對象是按順時針旋轉的例如postRotate( ) 將繞 x 軸將對象旋轉
  所有操作名都是以 post 開頭的表示當前 Transform 對象是從右邊與給定轉換矩陣相乘的 —— 矩陣操作的順序是非常重要的如果您向右旋轉 然後走兩步這時您所處的位置顯然與先走兩步再轉身不同您可以在各步行指令之後調用兩個 post 方法 postRotate() 和 postTranslate()從而獲得上面的步行指令調用順序決定了所獲得的步行指令由於使用的是後乘所以您最後使用的轉換會首先應用
  
  MG 有一個 Transform 類和一個 Transformable 接口所有快速模式的 API 均可接受 Transform 對象作為參數用於修改其關聯的 D 對象另外在保留模式下使用 Transformable 接口來轉換作為 D 世界一部分的節點在本系列的第 部分中將就此詳細討論
  
  清單 的示例展示了轉換
  
  清單 轉換
  
  /**
  * Renders the sample on the screen
  *
  * @param graphics the graphics object to draw on
  */
  protected void paint(Graphics graphics)
  {
  _graphicsdbindTarget(graphics);
  _graphicsdclear(null);
  _graphicsdrender(_cubeVertexData _cubeTriangles
  new Appearance() _cubeTransform);
  _graphicsdreleaseTarget();
  
  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 %= ;
  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 = f * (positiveDirection ? : );
  
  switch (axis)
  {
  case TRANSFORMATION_X_AXIS:
  _cubeTransformpostRotate(amount f f f);
  break;
  
  case TRANSFORMATION_Y_AXIS:
  _cubeTransformpostRotate(amount f f f);
  break;
  
  case TRANSFORMATION_Z_AXIS:
  _cubeTransformpostRotate(amount f f f);
  break;
  
  // no default
  }
  }
  else if (transformation == TRANSFORMATION_SCALE)
  {
  float amount = positiveDirection ? f : f;
  
  switch (axis)
  {
  case TRANSFORMATION_X_AXIS:
  _cubeTransformpostScale(amount f f);
  break;
  
  case TRANSFORMATION_Y_AXIS:
  _cubeTransformpostScale(f amount f);
  break;
  
  case TRANSFORMATION_Z_AXIS:
  _cubeTransformpostScale(f f amount);
  break;
  
  // no default
  }
  }
  else if (transformation == TRANSFORMATION_TRANSLATE)
  {
  float amount = f * (positiveDirection ? : );
  
  switch (axis)
  {
  case TRANSFORMATION_X_AXIS:
  _cubeTransformpostTranslate(amount f f);
  break;
  
  case TRANSFORMATION_Y_AXIS:
  _cubeTransformpostTranslate(f amount f);
  break;
  
  case TRANSFORMATION_Z_AXIS:
  _cubeTransformpostTranslate(f f amount);
  break;
  
  // no default
  }
  }
  }
  
  paint() 方法現有一個 Transform 對象 _cubeTransform該對象是 _graphicsdrender() 調用的第 個參數改進的 keyPressed() 方法中包含使用 transform() 交互地更改轉換的代碼GAME_C 鍵在旋轉平移和縮放立方體之間切換UP/DOWN 鍵更改當前轉換的 x 軸LEFT/RIGHT 更改 y 軸GAME_A/GAME_B 更改 z 軸按 FIRE 可將立方體重新設置為初始位置您可以在 TransformationsSamplejava 中找到完整的源代碼
  
  
示例立方體a) 旋轉b) 平移c) 縮放
   

  深度緩沖和投影
  
  這裡我想介紹兩個在使用轉換時已用到但未說明過的概念投影定義了將 D 對象映射到 D 屏幕的方法深度緩沖是根據對象與攝像機之間的距離正確渲染對象的一種方法
  
  要從攝像機的觀察點觀察渲染後的圖像您必須考慮攝像機的位置和方位D 世界轉換為攝像機空間在前面的示例代碼中我用 Camera 和 Transform 對象調用了 GraphicsDsetCamera()可將後者視為攝像機轉換或告訴 MSG 如何從世界坐標轉換為攝像機坐標的指令 —— 兩種定義都是正確的最後三維對象被顯示在二維屏幕上到這裡CamerasetPerspective() 告訴了 MG 在將 D 轉換為 D 空間時實現透視投影
  
  透視投影與真實世界中的情況比較類似當您俯視一條又長又直的道路時道路兩邊看上去似乎在地平線處交匯了距離攝像機越遠路旁的對象看起來也就越小您也可以忽略透視以相同大小繪制所有對象不管它們離得多遠這對於某些應用程序如 CAD 程序來說是很有意義的因為沒有透視可更容易地將精力集中在繪圖上要禁用透視投影可用 CamerasetParallel() 替換 CamerasetPerspective()
  
  在攝像機空間中對象的 z 坐標表示其與攝像機之間的距離如果渲染一些具有不同 z 坐標的 D 對象那麼您當然希望距離攝像機較近的對象比遠處的對象清晰通過使用深度緩沖對象可得到正確的渲染深度緩沖與屏幕有著相同的寬和高但用 z 坐標取代顏色值它存儲著繪制在屏幕上的所有像素與攝像機之間的距離然而MG 僅在一個像素比現有同一位置上的像素距離攝像機近時才將其繪制出來通過將進入的像素的 z 坐標與深度緩沖中的值相比較就可以驗證這一點因此啟用深度緩沖可根據對象的 D 位置渲染對象而不受 GraphicsDrender() 命令順序的影響反之如果您禁用了深度緩沖那麼必須在繪制 D 對象的順序上付出一定精力在將目標圖像綁定到 GraphicsD 時可啟用深度緩沖也可不啟用在使用接受一個參數的 bindTarget() 重載版本時默認為啟用深度緩沖在使用帶有三個參數的 bindTarget() 時您可以通過作為第二個參數的布爾值顯式切換深度緩沖的開關狀態
  
  您可以更改兩個屬性深度緩沖與投影如清單 所示
  
  清單 深度緩沖與投影
  
  /**
  * Initializes the sample
  */
  protected void init()
  {
  // Get the singleton for D rendering
  _graphicsd = GraphicsDgetInstance();
  
  // Create vertex data
  _cubeVertexData = new VertexBuffer();
  
  VertexArray vertexPositions =
  new VertexArray(VERTEX_POSITIONSlength/ );
  vertexPositionsset( VERTEX_POSITIONSlength/ VERTEX_POSITIONS);
  _cubeVertexDatasetPositions(vertexPositions f null);
  
  // Create the triangles that define the cube; the indices point to
  // vertices in VERTEX_POSITIONS
  _cubeTriangles = new TriangleStripArray(TRIANGLE_INDICES
  new int[] {TRIANGLE_INDICESlength});
  
  // Create parallel and perspective cameras
  _cameraPerspective = new Camera();
  
  float aspect = (float) getWidth() / (float) getHeight();
  _cameraPerspectivesetPerspective(f aspect f f);
  _cameraTransform = new Transform();
  _cameraTransformpostTranslate(f f f);
  
  _cameraParallel = new Camera();
  _cameraParallelsetParallel(f aspect f f);
  
  _graphicsdsetCamera(_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);
  behindOriginpostTranslate(f f f);
  Transform inFrontOfOrigin = new Transform(origin);
  inFrontOfOriginpostTranslate(f f f);
  
  // Disable or enable depth buffering when target is bound
  _graphicsdbindTarget(graphics _isDepthBufferEnabled );
  _graphicsdclear(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
  _cubeVertexDatasetDefaultColor(xFF);
  _graphicsdrender(_cubeVertexData _cubeTriangles
  new Appearance() inFrontOfOrigin);
  _cubeVertexDatasetDefaultColor(xFF);
  _graphicsdrender(_cubeVertexData _cubeTriangles
  new Appearance() origin);
  _cubeVertexDatasetDefaultColor(xFF);
  _graphicsdrender(_cubeVertexData _cubeTriangles
  new Appearance() behindOrigin);
  
  _graphicsdreleaseTarget();
  
  drawMenu(graphics);
  }
  
  /**
  * Handles key presses
  *
  * @param keyCode key code
  */
  protected void keyPressed(int keyCode)
  {
  switch (getGameAction(keyCode))
  {
  case GAME_A:
  _isPerspective = !_isPerspective;
  if (_isPerspective)
  {
  
  _graphicsdsetCamera(_cameraPerspective _cameraTransform);
  }
  else
  {
  _graphicsdsetCamera(_cameraParallel _cameraTransform);
  }
  break;
  
  case GAME_B:
  _isDepthBufferEnabled = !_isDepthBufferEnabled;
  break;
  
  case FIRE:
  init();
  break;
  
  // no default
  }
  
  repaint();
  }
  
  使用 GAME_A 鍵可在透視投影與平行投影之間切換GAME_B 可啟用或禁用深度緩沖完整的源代碼包含在 DepthBufferProjectionSamplejava 中 展示了不同設置下的效果
  
  
立方體a) 啟用深度緩沖根據與攝像機之間的距離進行渲染b) 禁用深度緩沖根據繪圖操作的順序進行渲染c) 使用平行投影而非透視投影進行渲染
   

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

  可使用法線計算光線後還需要告訴 MG 您需要什麼類型的光線光線來源於不同形式燈泡太陽手電筒等等在 MG 中的對應術語分別為全向光定向光和聚光
  
  全向光是從一個點發出的並平均地照射各個方向沒有燈罩的燈泡發出的就是這樣的光
  
  定向光向一個方向發出平行的光線太陽離我們的距離非常遠所以可以將其光線視為平行的定向光沒有位置只有方向
  
  手電筒或劇場中使用的聚光燈發射出的光線就是聚光其光線呈錐形與圓錐相交的平面上的對象會被照亮
  
  在真實世界中光線還會從對象上反射回來而將周圍照亮如果您打開臥室燈就會發現即便沒有能直接照射到床底下的光線但床下仍會被照亮Raytracer 通過追蹤從攝像機到光源的路徑而清晰真實地展示了圖像但需要很長時間要獲得交互式幀頻必須滿足一個簡單的模型環境光環境光以不變的頻率從各方向照亮對象您可以用環境光模擬前面的臥室場景將所有對象都照亮到一定程度從而提供了另外一個全向光源
  
  清單 描述了設置不同光線的方法
  
  清單 設置光線模式
  
  // Create light
  _light = new Light();
  _lightMode = LIGHT_OMNI;
  setLightMode(_light _lightMode);
  Transform lightTransform = new Transform();
  lightTransformpostTranslate(f f f);
  _graphicsdresetLights();
  _graphicsdaddLight(_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:
  lightsetMode(LightAMBIENT);
  lightsetIntensity(f);
  break;
  
  case LIGHT_DIRECTIONAL:
  lightsetMode(LightDIRECTIONAL);
  lightsetIntensity(f);
  break;
  
  case LIGHT_OMNI:
  lightsetMode(LightOMNI);
  lightsetIntensity(f);
  break;
  
  case LIGHT_SPOT:
  lightsetMode(LightSPOT);
  lightsetSpotAngle(f);
  lightsetIntensity(f);
  break;
  
  // no default
  }
  }
  
  在圖 您可以看到各種光線模式的不同效果分別以 種類型的光照射示例立方體這裡的光線均為白色就在攝像機前面朝向立方體的三個面
  
  
使用不同的光線照射立方體 a) 全向光b) 聚光c) 環境光d) 定向光
   

  全向光在面對光源的頂點處最亮然後逐漸暗淡下來另外聚光在聚光圓錐的邊緣處制造了強烈的明暗對比如果定義了一個足夠大的光錐那麼所得到的結果可能與全向光相同環境光從各個方向照亮立方體立方體看上去是平的這是因為缺乏陰影最後定向光使每面都具有不同的顏色每面內的顏色都相同這是因為光線是平行的
  
  照明並不精確否則聚光照亮的圓錐體范圍應該是圓形這是因為光線計算比較復雜手機的部件將簡化這一計算可以為立方體的各邊添加更多的三角形從而提高其顯示質量盡管三角形並不能定義一個可見的幾何圖形但可使 MG 擁有更多的控制點(要計算的數量也更多)
  
  材質
  
  通過光線可實現不同的效果一個閃閃發光的銀色球反射光線的方式與一張紙顯然不同MG 使用以下屬性為這些材質的特征建立模型
  
  環境反射由環境光源反射的光線
  漫反射反射光均勻地分散到各個方向
  放射光一個像熾熱的物體那樣發射光線的對象
  鏡面反射光線從有光亮平面的對象反射回來
  您可為各材質屬性設置顏色閃閃發光的銀色球的漫反射光線應該是銀色的其鏡面反射部分為白色材質的顏色與光線的顏色相融合從而得到最終的對象顏色如果您用藍光照射銀色球那麼球看上去應該略帶藍色
  
  清單 展示了使用材質的方式
  
  清單 設置材質
  
  // 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:
  materialsetColor(MaterialAMBIENT xFF);
  break;
  
  case COLOR_DIFFUSE:
  materialsetColor(MaterialDIFFUSE xFF);
  break;
  
  case COLOR_EMISSIVE:
  materialsetColor(MaterialEMISSIVE xFF);
  break;
  
  case COLOR_SPECULAR:
  
  materialsetColor(MaterialSPECULAR xFF);
  materialsetShininess();
  break;
  
  // no default
  }
  
  appearancesetMaterial(material);
  }
  
  setMaterial() 創建了一個新的 Material 對象通過使用各顏色組件標識符的 setColor() 設置顏色Material 對象隨後被指派給 Appearance 對象該對象用於調用 GraphicsDrender()盡管這裡沒有展示但您還可以使用 MaterialsetVertexColorTrackingEnable() 為環境反射和漫反射使用頂點顏色不必使用 MaterialsetColor()LightingMaterialsSamplejava 這一示例中實現了光線和材質按其中的鍵可以將不同的顏色與材質綜合感受不同的效果
  
  在圖 用全向光展示了不同的材質特征各截圖都將顏色組件設置為紅色以突出表現其效果
  
  
不同的顏色組件a) 環境反射b) 漫反射c) 放射光d) 鏡面反射
   

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

  將紋理坐標命名為(st)是為了與用於表示頂點位置的(xy)區分開來(從文字角度來講(uv)更為常用)坐標(st)定義為()的地方就是紋理的左上角而()位於右下角相應地如果您需要將立方體正面的左下角映射到紋理的左下角必須將紋理坐標()指派給頂點
  
  由於您定義了與紋理的角相關的紋理坐標所以任意大小的圖像都有相同的坐標MG 為最接近的 texel 插入 之間的值例如 表示紋理的中點如果紋理坐標超過 ~ 的范圍MG 會提示您確認坐標既可環繞(例如 的效果相同)也可采用加強方式所謂加強也就意味著任何小於 的值都按 使用任何大於 的值都按 使用紋理的寬和高可有所不同但必須是 的冪如圖 中的 部件必須至少支持 的紋理大小這是 MG 的一個可選屬性
  
  GraphicsDgetProperties() 返回一個 Hashtable其中填充了特定於部件的屬性如最大紋理維度或支持的最大光源數getProperties() 的文檔包含一個屬性及其最低需求的清單在使用超過這些值的屬性之前應該檢查設備的部件是否能提供支持
  
  清單 展示了紋理的使用
  
  清單 使用紋理 部分初始化
  
  /** The cubes vertex positions (x y z) */
  private static final byte[] VERTEX_POSITIONS = {
                 // front
             // back
               // right
               // left
  
                // top
              // bottom
  };
  
  /** Indices that define how to connect the vertices to build
  * triangles */
  private static final int[] TRIANGLE_INDICES = {
       // front
       // back
      // right
     // left
     // top
     // bottom
  };
  
  /** Lengths of triangle strips in TRIANGLE_INDICES */
  private static int[] TRIANGLE_LENGTHS = {
  
  };
  
  /** File name of the texture */
  private static final String TEXTURE_FILE = /texturepng;
  
  /** The texture coordinates (s t) that define how to map the
  * texture to the cube */
  private static final byte[] VERTEX_TEXTURE_COORDINATES = {
           // front
           // back
           // right
           // left
           // top
           // bottom
  };
  
  /** First color for blending */
  private static final int COLOR_ = xFF;
  
  /** Second color for blending */
  private static final int COLOR_ = xFF;
  
  /**
  * Initializes the sample
  */
  protected void init()
  {
  // Get the singleton for D rendering
  _graphicsd = GraphicsDgetInstance();
  
  // Create vertex data
  _cubeVertexData = new VertexBuffer();
  
  VertexArray vertexPositions =
  new VertexArray(VERTEX_POSITIONSlength/ );
  vertexPositionsset( VERTEX_POSITIONSlength/ VERTEX_POSITIONS);
  _cubeVertexDatasetPositions(vertexPositions f null);
  
  VertexArray vertexTextureCoordinates =
  
  new VertexArray(VERTEX_TEXTURE_COORDINATESlength/ );
  vertexTextureCoordinatesset(
  VERTEX_TEXTURE_COORDINATESlength/ VERTEX_TEXTURE_COORDINATES);
  _cubeVertexDatasetTexCoords( vertexTextureCoordinates f null);
  
  // Set default color for cube
  _cubeVertexDatasetDefaultColor(COLOR_);
  
  // 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();
  camerasetPerspective(f aspect f f);
  Transform cameraTransform = new Transform();
  cameraTransformpostTranslate(f f f);
  _graphicsdsetCamera(camera cameraTransform);
  
  // Rotate the cube so we can see three sides
  _cubeTransform = new Transform();
  _cubeTransformpostRotate(f f f f);
  _cubeTransformpostRotate(f f f f);
  
  // Define an appearance object and set the polygon mode
  _cubeAppearance = new Appearance();
  _polygonMode = new PolygonMode();
  _isPerspectiveCorrectionEnabled = false;
  _cubeAppearancesetPolygonMode(_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
  ImageD imageD = (ImageD) Loaderload(TEXTURE_FILE)[];
  _cubeTexture = new TextureD(imageD);
  _cubeTexturesetBlending(TextureDFUNC_DECAL);
  
  // Index is used because we have only one texture
  _cubeAppearancesetTexture( _cubeTexture);
  }
  catch (Exception e)
  {
  Systemoutprintln(Error loading image + TEXTURE_FILE);
  eprintStackTrace();
  }
  }
  
  在 init() 中向 VertexBuffer 增加了定義為類的靜態成員的紋理坐標與在照明示例中的情況類似我為立方體的每個面都使用了 個向量以將各頂點映射到紋理的一個角注意我使用了比例 作為 _cubeVertexDatasetTexCoords() 的第三個參數這也就告訴 MG 將所有紋理坐標都乘以此值實際上紋理僅使用了立方體面的四分之一這樣做的目的是展示 MG 的加強和環繞特性如果是加強那麼僅在左上角繪制如果是環繞那麼紋理圖案將填充滿整個面
  
  紋理是用 Loaderload() 載入的並指派給 TextureD 對象您還應使用 MIDP 的 ImagecreateImage()但如果您想從 Java Archive(JAR)文件中讀取紋理那麼 Loader 類是最快的方式所得到的 TextureD 對象隨後被設置為立方體外觀的紋理
  
  在進行紋理化處理時您可能依然希望使用通過照明獲得或直接指派給頂點的顏色出於此方面的考慮MG 提供了各種混色功能可通過調用 _cubeTexturesetBlending() 來設置在 init() 中我使用了 TextureDFUNC_DECAL它將根據 α 值將紋理與基本頂點顏色相混合 中紋理圖像的灰色位透明度為 %這裡沒有設置頂點顏色或使用照明而是使用 _cubeVertexDatasetDefaultColor() 為立方體設置了一個默認顏色這也就意味著立方體中的所有三角形都將使用同樣的顏色通過混色您也可以在各紋理上使用多重紋理從而獲得更豐富的效果
  
  我還內置了一個可選的 MG 特性如照明部分中所示渲染的質量取決於您所使用的三角形數量 —— 頂點之間的距離越小插值效果就越好這對於紋理來說也是成立的高質量對於紋理而言就意味著紋理在不失真的情況下映射如圖 所示的紋理是有缺陷的因為其中包含直線顯然會發生失真的情況MSG 提供了一種在處理能力方面代價低廉的方法來解決這一問題可用 PolygonModesetPerspectiveCorrectionEnable() 設置可選的透視修正標志如清單 所示
  
  清單 使用紋理 部分交互式更改透視修正環繞模式及混色
  
  /**
  * Checks whether perspective correction is supported
  *
  * @return true if perspective correction is supported false otherwise
  */
  protected boolean isPerspectiveCorrectionSupported()
  {
  Hashtable properties = GraphicsDgetProperties();
  Boolean supportPerspectiveCorrection =
  (Boolean) propertiesget(supportPerspectiveCorrection);
  
  return supportPerspectiveCorrectionbooleanValue();
  }
  
  /**
  * Handles key presses
  *
  * @param keyCode key code
  */
  protected void keyPressed(int keyCode)
  {
  switch (getGameAction(keyCode))
  {
  case LEFT:
  _cubeTransformpostRotate(f f f f);
  break;
  
  case RIGHT:
  _cubeTransformpostRotate(f f f f);
  break;
  
  case FIRE:
  init();
  break;
  
  case GAME_A:
  if (isPerspectiveCorrectionSupported())
  {
  _isPerspectiveCorrectionEnabled = !_isPerspectiveCorrectionEnabled;
  _polygonModesetPerspectiveCorrectionEnable(
  _isPerspectiveCorrectionEnabled);
  }
  break;
  
  case GAME_B:
  if (_cubeTexturegetWrappingS() == TextureDWRAP_CLAMP)
  {
  _cubeTexturesetWrapping(TextureDWRAP_REPEAT
  TextureDWRAP_REPEAT);
  }
  else
  {
  _cubeTexturesetWrapping(TextureDWRAP_CLAMP
  TextureDWRAP_CLAMP);
  }
  break;
  
  case GAME_C:
  if (_cubeVertexDatagetDefaultColor() == COLOR_)
  {
  _cubeVertexDatasetDefaultColor(COLOR_);
  }
  else
  {
  _cubeVertexDatasetDefaultColor(COLOR_);
  }
  break;
  
  // no default
  }
  
  repaint();
  }
  
  在示例中isPerspectiveCorrectionSupported() 用於檢查部件是否支持透視修正如果支持您可在 keyPressed() 中交互地切換標志的開關狀態這裡還增加了一個更改紋理映射到立方體的方式(加強或重復)的選項以及一個更改混色的選項對混色的更改示范了可以容易地將顏色與紋理相混合以獲得更豐富的效果在 TexturesSamplejava 中可以看到完整的示例
  
  圖 展示了使用不同選項的紋理映射效果
  
  
紋理a) 無透視修正b) 有透視修正c) 加強而非平鋪d) 用綠色代替蘭色進行混色
   

  結束語
  
  本文中介紹了大量基礎知識包括使用頂點數據創建立方體使用攝像機為立方體照相在立方體上應用光線和材質利用紋理創建具有真實感的立方體的方法等詳細信息給出了許多立方體作為示例
  
  在論證概念時立方體是一種極好的示例但它並不是復雜的 D 設計的裡程碑在介紹過程中我從游戲的角度強調了 D 圖像如果像示例那樣通過手工組合頂點數據那麼設計一個復雜的游戲世界將成為一項令人望而卻步的工作您需要一種方法通過建模工具來設計 D 場景並將數據導入程序
  
  導入模型後必須再尋求一種組織數據的方法如果使用 VertexBuffer 方法您必須記住所有的轉換以及對象之間的關系比如說上臂與下臂相連而下臂應該與手相連您必須對應地安置手臂與手MG 提供的一種場景圖形 API —— 保留模式 —— 簡化了此類任務通過保留模式您可以為全部對象及其屬性建模在本系列的第 部分中將就此進行詳細論述
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26106.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.