該程序在 2005 年又重新用 Java 編寫了一次,主要設計工作由 David J. Eck 完成。其 Java 版本被稱作 3D-XplorMath-J。它可在作為獨 立程序運行,也可以作為 網站 上的一系列 applet 運行。該項目受到國際自然科學基金 (DUE Award #0514781) 一定程度上的支持。
OpenGL 集成中的設計問題
3D-XplorMath-J 最初使用 Java Graphics2D,我們的目標是找到集成 OpenGL 圖形的最簡單方法。3D-XplorMath-J 使用 接口 Renderer3D 定義的渲染程序來繪圖。我們創建了另外一個渲染程序類,使該程序除了能執行現有 Java Graphics2D 渲染以外,還能執行 OpenGL 渲染。這 個新的渲染程序將設置 OpenGL 光照、影像和視點轉換,然後使用 OpenGL 命令進行渲染和繪制。
OpenGL 提供了硬件加速 3D 繪圖,借助改進的圖形和極快的速度,能使用 Graphics2D 實現 3D 繪圖。OpenGL 通過 JOGL API 使用 Java 實現。圖 1 中的圖像給出了改進圖形的可視化表示,我們是通過 JOGL 實現的。從上面兩幅圖可以看出,JOGL 給出的蝸牛殼表示形式比 Java Graphics2D 繪制的更加清晰。但是,這點區別在不是很復雜的對象中並不明顯。對於正在旋轉的對象,這點區別會變得很明顯,即使是卷形墊 ,這從下面兩幅圖中可以看出。
圖 1. 圖形比較。左上方:靜止的蝸牛殼,使用 JOGL 繪制。右上方:靜止的蝸牛殼,使用 Java Graphics2D 繪制。左下方:動態的卷形 墊,使用 JOGL 繪制。右下方:動態的卷形墊,使用 Java Graphics2D 繪制。
JOGL 中有三個類表示 OpenGL “可繪制區域”,即幾個可以繪制 3D 圖像的地方。它們是 GLCanvas、GLJPanel 和 GLPbuffer。其中, GLCanvas 和 GLJPanel 是兩個常用的類,但在大型復雜的項目中,可能需要更改大量代碼後,集成才能成功。GLPbuffer 是三個類中最少知道 的一個,但它擁有一些獨特的功能,足以引起 Java 程序員的重視。在本例中,使用一個 pbuffer 來渲染對象似乎很簡單,因為我們可以使用 與 Java Graphics2D 相同的框架,只要稍微修改為繪制到 pbuffer(在內存中存儲屏幕以外的圖片)即可。這樣的圖片然後可以作為一個標准 BufferedImage 被檢索到,該 BufferedImage 可以被復制到屏幕。
GLCanvas 和 GLJPanel 是 GUI 組件。GLCanvas 更兼容 AWT,GLJPanel 更兼容 Swing。在 GLCanvas 中繪制會更快一點,但它是一個“重 量級”組件,在 Swing 中使用比較困難。在 Amy Fowler 和 Chris Campbell 合著的一些文章中對此問題進行了討論。在 3D-XplorMath-J 中 使用這些類需要用 GLCanvas 或 GLJPanel 替換 JPanel。遺憾的是,JPanel 更多用在 3D 繪圖中。例如,它用於顯示 BufferedImage,這些 事情用 GLCanvas 或 GLJPanel 不是那麼容易實現。請注意:至少在 Java 5.0 中是這樣的。文章最後,我們將簡要討論 Java 6.0 中的一些 變化。
另一方面,JPanel 可以在不破壞程序 Graphics2D 繪制框架的情況下使用。GLPbuffer 表示內存中一個可用作 OpenGL 命令繪制界面的區 域,非常類似於 Graphics2D 的 BufferedImage。這樣,就可以獲得用 BufferedImage 形式繪制的圖像副本。BufferedImage 然後可以使用標 准的 Graphics2D 技術復制到屏幕。在這篇文章中,我們將說明如何實現這一點,並討論這樣做的性能意義。使用 pbuffer 最主要是考慮到它 仍然是 JOGL 的一個試驗方面,因而受各種平台不同程序的支持。繪制到緩沖區而不是直接繪制到 GLJPanel 或 GLCanvas 會變慢也是我們考 慮的一點。在 Linux、Mac 和 Windows 環境中經過測試後可以確定,這三者之間的差異很小,不會影響到 pbuffer 的使用。運行同一動畫, 繪制 500 幀,每一幀畫 500 個任意大小的球,每一種平台得到的速度如下:
類 整個動畫的估計時間 每幀的估計時間 GLCanvas 39285ms=39.285s 78.57ms=.07857s GLJPanel 42325ms=42.325s 84.65ms=.08465s GLPbuffer 50945ms=50.945s 101.89ms=.10189s 類 整個動畫的估計時間 每幀的估計時間 GLCanvas 48795ms=48.795s 97.59ms=.09759s GLJPanel 53161ms=53.161s 106.322ms=.106322s GLPbuffer 82221ms=82.221s 164.442ms=.164442s 類 整個動畫的估計時間 每幀的估計時間 GLCanvas 50813ms=50.813s 101.626ms=.101626s GLJPanel 67704ms=67.704s 135.408ms=.135.408s GLPbuffer 70703ms = 70.703s 141.406ms=.141406s這些表顯示了使用 GLPbuffer 消耗的時間並不長,在 Mac 平台上表現得比其他平台更顯著。因此我們決定使用 pbuffer 來試著集成,利 用它的試驗特性來碰碰運氣。用於生成這些表的程序代碼可從 示例代碼 下載。其中還有一個交互式程序,用於以交互方式比較這兩種技術。
Pbuffer
從 pbuffer 獲得一個 BufferedImage 後,再與現有數學可視化框架集成就變得非常簡單了。剩下的只是設置一個 OpenGL 渲染程序來繪制 到 pbuffer,從緩沖區獲得緩沖圖像,然後將 BufferedImage 復制到屏幕上。下面說明操作方法。
要創建 pbuffer,使用如下代碼,在繪制圖片(寬和高是窗口的寬和高)之前調用 OpenGL 渲染程序。
if (buf == null || bufw != width || bufh != height){
if (buf != null) { // clean the old buffer
context.destroy(); //context is type GLContext
buf.destroy(); // buf is type GLPbuffer
}
GLDawableFactory fac = GLDrawableFactory.getFactory();
GLCapabilities glCap = new GLCapabilities();
// Without line below, there is an error on Windows.
glCap.setDoubleBuffered(false);
//makes a new buffer
buf = fac.createGLPbuffer(glCap, null, width, height, null);
//save size for later use in getting image
bufw = width;
bufh = height;
//required for drawing to the buffer
context = buf.createContext(null);
}
context 是一個渲染到任何 OpenGL 可繪制區域所需的抽象。JOGL 事件驅動繪制框架自動處理上下文,但它們需要在該框架外部繪制,即 本例中使用 pbuffer 的情況。手動控制上下文需要從可繪制對象獲取上下文,並使它在渲染到時成為當前的。
我們發現還需要一個緩沖區清除檢查,如下所示:
if (buf != null) {
context.destroy();
buf.destroy();
}
我們發現,為避免創建太多 pbuffer 而使速度變慢和發生沖突,這一個檢查很重要。destroy() 方法釋放被上下文和 pbuffer 占用的資源 。這在 JOGL 中顯然不會自動完成。
還應該在程序開頭做一個檢查,以確保能創建一個 GLPbuffer:
if(!GLDrawableFactory.getFactory().canCreateGLPbuffer())
//Disable the using of OpenGL or at least by way of a Pbuffer
這個檢查很重要,因為 pbuffer 不一定在所有計算機上受支持,而它要使用硬件來加快渲染速度。該檢查查看計算機上的顯卡是否支持 pbuffer,但從我們在不同實驗機器上的測試來看,試圖創建一個 GLPbuffer 有時會生成一條錯誤,即使該方法返回的是 true。既然這樣,似 乎最好只試著創建 pbuffer 並捕獲發生的所有異常。
現在渲染到 pbuffer,我們使用在緩沖區創建的上下文,並使它成為當前的。這確保隨後的 OpenGL 命令將繪制到 pbuffer,而不是其他地 方。在此 OpenGL 渲染程序中,我們在渲染程序繪制方法開頭調用上下文:
context.makeCurrent();
下一個任務是獲取一個繪制使用的 GL 對象。直接使用 pbuffer 即可實現這一點,剩下的遵循常規 JOGL 語法。
GL gl = buf.getGL();
從 pbuffer 獲取圖形通過使用 JOGL 類 Screenshot 來完成,該類用於獲取 OpenGL 應用程序的屏幕快照。不管名稱如何,它只返回當前 OpenGL 可繪制區域而不是整個屏幕的圖像。通過調用 Screenshot.readToBufferedImage(bufw,bufh),我們獲取了當前 GL Drawable(我們的 pbuffer)的屏幕快照作為緩沖圖像。另請注意,該 pbuffer 的上下文必須是當前的。現在我們有了緩沖圖像,然後便可以使用 Java Graphics2D 來將圖像繪制到屏幕了。
context.makeCurrent();
BufferedImage img = Screenshot.readToBufferedImage(bufw,bufh);
context.release();
g.drawImage(img,0,0,null);
因為設計 3D-XplorMath-J 是為了使用 Java Graphics2D,所以最有用的部分是在項目中使用 pbuffer。既然能從 pbuffer 獲取緩沖圖像 ,那麼在渲染程序外部就無需更改任何代碼,只是要創建一個 OpenGL 渲染程序來進行 3D 繪制,而不是使用 Graphics2D 渲染程序。
速度考慮和一些解決方案
因為數學對象很復雜,所以渲染這些對象會非常慢,特別是在渲染一個曲面並對它進行實時渲染時。盡可能以多種方式旋轉和查看數學對象 很大程度上是 3D-XplorMath 項目的目標,因此我們需要一種方法來提高實時渲染數學對象的效率。部分無效渲染歸因於使用 pbuffer,但有 了 OpenGL 功能後,繪圖仍然太慢。要縮短渲染時間,我們看一下 OpenGL 顯示列表 以及它們的使用方式,並看看使用它們會達到怎樣的速度 。
顯示列表允許以優化形式存儲 OpenGL 命令以便以後執行,這在存儲將使用多次的幾何形狀和狀態更改時特別有用,如旋轉數學對象時。這 在本例中很有用,因為每次在繪制旋轉對象時可以調用同一顯示列表。既然這對組織代碼很適用,那麼它是否能按我們的需要提高速度呢?
顯示列表由一個唯一的整數標識,這個整數是使用 gl.glGenList() 命令創建的,其中 gl 是一個 GL 類型的對象。接著我們使用列表標識 符和需要的模式創建一個新的列表。
displayListID = gl.glGenLists(1); // type int
gl.glNewList(displayListID, GL.GL_COMPILE);
glNewList 後的 OpenGL 命令被添加到該列表中,當要存儲在列表中的命令完整後,我們使用
gl.glEndList(); 來結束列表。
最後,在需要執行顯示列表中的高速緩存代碼時,使用列表的標識符調用該列表即可。
gl.glCallList(displayListID);
學會如何使用顯示列表後,在了解如何將它添加到 3D OpenGL 渲染程序之前,還應該編寫一個測試來看它是否真的提高了效率。以之前用 500 幀繪制 500 個球的測試程序為例,我們實現同樣的測試,用額外的顯示列表來存儲繪制球的命令,並調用該列表將每個球繪制到屏幕。結 果我們發現速度大大提高了。
簡單的球繪制程序只使用一個 pbuffer 和 0 個顯示列表來用 500 個不同的幀繪制 500 個球,在 Linux 環境下大約花了 50s 的時間。使 用顯示列表來實現同樣的程序卻只花了12s,所以我們決定將顯示列表集成到項目中來使它更容易。
將顯示列表添加到 3D-XplorMath-J 相當簡單。其思想是將渲染對象的代碼放入顯示列表中,在渲染程序需要繪制到屏幕時,通過調用顯示 列表來實現。這意味著,當對象僅需要轉換時,如在旋轉對象時,我們只需將轉換應用到列表中,而不是使用對象新的方向來重新渲染它。
使用顯示列表能大大提高旋轉曲面的速度,但使用顯示列表後還是比較慢。我們發現,這是因為我們使用 glBegin(GL_POLYGON) 來繪制單 個多邊形。使用 glBegin(GL_QUAD_STRIP) 來繪制曲面後,速度就提高了。GL_QUAD_STRIP 是一個幾何原型,是一次由兩個頂點定義的多邊形 閉邊。這點微小的改動使得與繪制單個對變形相比,速度大大提高了。
了解將這兩項技術實現到項目所帶來的速度提高後,使用 pbuffer 付出的代價相比而言就很小了。現在我們就有了一個能很快執行實時渲 染的程序,甚至對復雜的幾何對象也是如此。
快速了解 Java 6 和 OpenGL
在 Java SE 6 中,JOGL 和 Java 2D 可以直接同時使用。Java 6 現在允許 Swing 對象覆蓋 OpenGL 渲染。現在,渲染 3D OpenGL 圖形可 以直接在 Java 2D 圖形上完成,類似地,Java 2D 圖形也可以直接在 3D OpenGL 圖形上繪制。到目前為止,3D-XplorMath-J 項目會繼續使用 Java 5,但將來轉向 Java 6 會有更多設計選項來集成 OpenGL 渲染,這會更可靠和可有效。Java 6 使得使用 GLJPanel 或 GLCanvas 相當自 然,這意味著無需再依賴從 pbuffer 獲取緩沖圖像來獲得一點小小的速度提高了。不使用 pbuffer 將避免不能在一些計算機上使用 pbuffer 的麻煩,使 OpenGL 渲染能被更多人利用。Java 6 在圖形和 Java 編程語言方面無疑是一大進步,應該通過它來結合使用 OpenGL 和 Java。 但對於仍然需要 Java 5 的程序而言,在轉向 Java 6 之前,pbuffer 是使用 OpenGL 和 Java 2D 的選擇。