背景
為了創建一個圖形增強的程式,從而出售給使用各種不同作業系統的用戶,程式設計師有一個選擇——OpenGL。GL代表圖形庫(graphics library)。OpenGL是SGI(美國圖形工作站生產廠商)的註冊商標。OpenGL顯示了它是一個跨平台的C語言編程API。但是事實上,在編程接口上,它是一個與硬體無關的規範。OpenGL是用來製圖的,速度非常快。大多數場合下,它是硬體加速的。看起來好像OpenGL可以實現一切你想要完成的圖形界面。
不幸的是,OpenGL是為C語言而寫的。不得不承認,C語言不是用來編寫複雜應用程式的流行語言。關於OpenGL一個最大的缺點就是:如果不創建一個視窗(用來把你的圖形放入其中),就什麼都做不了。但是OpenGL沒有提供給創建視窗的方法。這使得OpenGL對於初學者來說顯得比較難。
幸運地是,出現了GLUT (OpenGL Utility Toolkit)(OpenGL工具包)。它被用來輕鬆應對視窗、按鈕以及用戶事件。儘管如此,對於想要使用面向對象的編程的程式設計師來說,學習用C或者C++來編寫OpenGL程式仍然是一件痛苦的事。
JOGL
Java 也許是最流行的真正的面向對象的程式語言。有許多用Java去結合OpenGL的嘗試,但是第一個被大家認可並注意的是Java對於OpenGl的綁定(Java Bindings for OpenGL), 或者稱為JOGL。理由是它得到Sun(Java的創建者)和SGI(OpenGL的創建者)的支持。如今,Sun的遊戲開發小組正在開發JOGL。它是以肯·拉塞爾和克里斯·克蘭開發的Jungle開始的。拉塞爾是Sun的員工,研發“HotSpot虛擬機”,擁有多年的三維經驗。克蘭則研發“荒謬的遊戲”,對三維圖形學也相當有經驗。
OpenGL 被用來展示三維模型。它強大、快速,而且可能是自Swing出現以來最棒的一樣東西。通過JOGL來使用OpenGL,你可以製作出很酷的遊戲或是模型位置什麼的,而在這之前創建它們需要非常昂貴的成本。有人寫了很厚很厚的書來描述OpenGL,當你熟悉了它們以後這些書會很有用,但現在不行。你必須學習展現在你面前的OpenGL是如何使用Java API的。同樣你還得看一下關於net.java.games.jogl.*的基礎介紹,可能還得補習一下數學知識。
獲取JOGL
就是要找到你的作業系統所對應的包,並進行解壓縮。這個是可以從https://jogl.dev.java.net/找到的,進入網站後點左側的文檔和檔案,我下載了Release Builds 2008\JSR-231 1.1.1 - May 22 (15)裡面的jogl-1.1.1-windows-i586.zip,解壓就得到要的jogl.jar和其他檔案了。
JOGL的Javadocs
同樣可以在和JOGL 的binary 發布版一樣的位置獲得Javadocs。Javadocs將會以類似jogl-1.0-usrdoc.tar的名字而命名。如果你瀏覽一下net.java.games.jogl包,你很快會注意到有些類非常大。GL便是一個完美的例子。別被這個嚇跑了,你很快能發現只需一點點JOGL的知識,你就可以完成一些相當複雜的事了。現在你需要掃視一下的類有:
*GLDrawable
*GLCanvas
*GLJPanel
*GLCapabilities
*GLDrawableFactory
這些是連線圖形世界基本的接口。如果你還記得,前面我提到對於初學OpenGL的人來說,有一個很大的缺點,那就是缺乏視窗系統的標準。對應於C語 言,GLUT起到了相當大的作用。而我們則有Swing和AWT(抽象視窗工具箱)。很可能你已經使用過AWT或者Swing了,所以你不會感到自己在從 頭學起。這是件非常好的事情。在通過了非常簡短的關於把JOGL組件放置到螢幕上的介紹以後,我們不需要多長時間就可以運行出一個相當酷而且流行的程式 了。
GlueGen
應該意識到,OpenGL是為C程式設計師而寫的。這意味著Java想要利用它,必須要用到本機接口。不那么有趣的JNI(Java本機接口)必須用來進行此 連線。OpenGL太大了,手寫所有的連線太費時。想稍微做出一點複雜的程式,有許多特別出售的特性,OpenGL則保持改進,那意味著得有相應的變化來 跟上OpenGL的步伐。簡而言之,對於任何試著寫與OpenGL保持同步,包含所有Java到本機的接口的代碼的嘗試,是非常困難的。讓我們進入JOGL家族看看。他們打算利用C頭檔案寫一些代碼來實現一切JNI做的事。他們管這個叫做GlueGen。GlueGen解析C頭檔案然後魔法般地創建出Java和JNI代碼以便連線到本機庫。這意味著OpenGL的升級可以迅速地在JOGL里體現。
Hello World!
這個“你好世界”程式將檢驗安裝是否全部或者一部分安裝正確。回憶一下安裝JOGL有2個部分,分別是jar檔案里的Java庫以及其它庫的本機代碼。以下是程式:
import net.java.games.jogl.*;
public class HelloWorld {
public static void main (String args[]) {
try {
System.loadLibrary("jogl");
System.out.println(
"Hello World! (The native libraries are installed.)"
);
GLCapabilities caps = new GLCapabilities();
System.out.println(
"Hello JOGL! (The jar appears to be available.)"
);
} catch (Exception e) {
System.out.println(e);
}
}
}首先,這個程式測試了本機庫和Java庫是否已經安裝正確了。只有當jogl.jar和本機庫(名字諸如libjogl.jnilib或者 jogl.dll)兩者都安裝好了的時候,JOGL才算是安裝完全的。如果本機庫不可用,程式會拋 java.lang.UnsatisfiedLinkError例外。如果classpath里沒有安裝JAR,程式則根本編譯都通不過。Javac編譯 器會報諸如此類的錯“net.java.games.jogl包不存在”。當這個程式編譯通過且運行起來沒有異常的話,你可以繼續學習JOGL了。
一個好的模板
當你對JOGL感到思維混亂的時候,讓我們來繼續看幾個類,你可以把它們當成有用的模板來使用。我已經不止一次把它們當成模板用了。你可以隨心所欲地使用它們。這個模板由兩個類組成。第一個是如下所示的SimpleJoglApp,第二個是在簡短說明之後的SimpleGLEventListener。你必須輸入兩個類來編譯模板。主程式如下:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import net.java.games.jogl.*;
/**
* This is a basic JOGL app. Feel free to
* reuse this code or modify it.
* 這是個基礎的JOGL程式,你可以隨意重用該代碼或者修改它。
*/
public class SimpleJoglApp extends JFrame {
public static void main(String[] args) {
final SimpleJoglApp app = new SimpleJoglApp();
// show what we've done
//看一下我們做了什麼
SwingUtilities.invokeLater (
new Runnable() {
public void run() {
app.setVisible(true);
}
}
);
}
public SimpleJoglApp() {
//set the JFrame title
//設定JFrame標題
super("Simple JOGL Application");
//kill the process when the JFrame is closed
//當JFrame關閉的時候,結束進程
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//only three JOGL lines of code ... and here they are
//只有三行JOGL代碼 ... 如下
GLCapabilities glcaps = new GLCapabilities();
GLCanvas glcanvas =
GLDrawableFactory.getFactory().createGLCanvas(glcaps);
glcanvas.addGLEventListener(new SimpleGLEventListener());
//add the GLCanvas just like we would any Component
//像其它組件一樣把GLCanvas加入
getContentPane().add(glcanvas, BorderLayout.CENTER);
setSize(500, 300);
//center the JFrame on the screen
//使JFrame顯示在螢幕中央
centerWindow(this);
}
public void centerWindow(Component frame) {
Dimension screenSize =
Toolkit.getDefaultToolkit().getScreenSize();
Dimension frameSize = frame.getSize();
if (frameSize.width > screenSize.width )
frameSize.width = screenSize.width;
if (frameSize.height > screenSize.height)
frameSize.height = screenSize.height;
frame.setLocation (
(screenSize.width - frameSize.width ) >> 1,
(screenSize.height - frameSize.height) >> 1
);
}
}
代碼就是這些。讓我們把注意力集中在第一個類中與JOGL相關的三行代碼上。首先:
這決定了我們的JOGL庫和JVM可以使用哪些OpenGL/圖形特色。
接著:
GLCanvas glcanvas =
GLDrawableFactory.getFactory().createGLCanvas(glcaps);我們不能創建GLCanvas或者GLJPanel。我們得用GLDrawableFactory來創建它們。所以我們用GLDrawableFactory的靜態方法getFactory()取得了GLDrawableFactory。
現在我們有GLDrawableFactory了。所以我們用createGLCanvas()方法來創建了可以往上畫畫的GLCanvas。如果我們不需要AWT組件,而是Swing組件,則可以用createGLJPanel()方法。
注意我們把先前創建的GLCapabilities對象傳了進去。這可以使我們創建的GLDrawable適當的所創建。
最後,我們準備往GLCanvas上加GLEventListener。
我 們對GLEventListener的實現是SimpleGLEventListener。它負責當接到GLDrawable或我們的或只是 GLCanvas的調用時,所需要完成的所有繪圖工作。你將會看到,我不打算在這個程式里畫任何東西。下面是GLEventListener的代碼:
import java.awt.*;
import java.awt.event.*;
import net.java.games.jogl.*;
/**
* For our purposes only two of the
* GLEventListeners matter. Those would
* be init() and display().
* 為了達到我們的目的,GLEventListener中只有兩個方法有用。
* 它們是init()和display()。
*/
public class SimpleGLEventListener implements GLEventListener
{
/**
* Take care of initialization here.
* 注意這裡的初始化。
*/
public void init(GLDrawable drawable) {
}
/**
* Take care of drawing here.
* 注意這裡的繪圖。
*/
public void display(GLDrawable drawable) {
}
/**
* Called when the GLDrawable (GLCanvas
* or GLJPanel) has changed in size. We
* won't need this, but you may eventually
* need it -- just not yet.
* 當GLDrawable(GLCanvas或GLJPanel)大小改變時被調用。
* 我們不需要它,但你可能最後會用到——雖然現在並不需要。
*/
public void reshape(
GLDrawable drawable,
int x,
int y,
int width,
int height
) {}
/**
* If the display depth is changed while the
* program is running this method is called.
* nowadays this doesn't happen much, unless
* a programmer has his program do it.
* 當程式運行時顯示深度被改變的時候此方法被調用。
* 現在這種事發生得不多,除非程式裡面觸發此事。
*/
public void displayChanged(
GLDrawable drawable,
boolean modeChanged,
boolean deviceChanged
) {}
}
以 上就是我們要完成的JOGL核心工作。注意下面的UML圖。SimpleJoglApp是一個JFrame。它容納了GLDrawable,實際上是一個 GLCanvas,但不要那樣稱呼它。我們加入了SimpleGLEventListener。SimpleGLEventListener實現了對於 GLCanvas的GLEventListener,這樣當它想執行任何的OpenGL 工作的時候,GLCanvas就可以知道。GLDrawables能自動執行,所以你確實得使你的GLEventListener最最佳化。
這個程式運行起來可能會根據你的作業系統顯得有點亂七八糟。這是預料之中的,因為你在這裡只是往螢幕上顯示隨機的記憶體。所以恭喜你具有了圖形創新的才能了。
實例
這就是你接下來的程式。請確保你輸入了所有的代碼到你的編輯器中。調試這些程式可以快速地使你明白它們的工作原理。import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import net.java.games.jogl.*;
/**
* This is a basic JOGL app. Feel free to
* reuse this code or modify it.
* 這是個基礎的JOGL程式,你可以隨意重用該代碼或者修改它。
*/
public class SecondJoglApp extends JFrame {
public static void main(String[] args) {
final SecondJoglApp app = new SecondJoglApp();
//show what we've done
//看一下我們做了什麼
SwingUtilities.invokeLater (
new Runnable() {
public void run() {
app.setVisible(true);
}
}
);
}
public SecondJoglApp() {
//set the JFrame title
//設定JFrame標題
super("Second JOGL Application");
//kill the process when the JFrame is closed
//當JFrame關閉的時候,結束進程
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//only three JOGL lines of code ... and here they are
//只有三行JOGL代碼 ... 如下
GLCapabilities glcaps = new GLCapabilities();
GLCanvas glcanvas =
GLDrawableFactory.getFactory().createGLCanvas(glcaps);
glcanvas.addGLEventListener(new SecondGLEventListener());
//add the GLCanvas just like we would any Component
//像其它組件一樣把GLCanvas加入
getContentPane().add(glcanvas, BorderLayout.CENTER);
setSize(500, 300);
//center the JFrame on the screen
//使JFrame顯示在螢幕中央
centerWindow(this);
}
public void centerWindow(Component frame) {
Dimension screenSize =
Toolkit.getDefaultToolkit().getScreenSize();
Dimension frameSize = frame.getSize();
if (frameSize.width > screenSize.width )
frameSize.width = screenSize.width;
if (frameSize.height > screenSize.height)
frameSize.height = screenSize.height;
frame.setLocation (
(screenSize.width - frameSize.width ) >> 1,
(screenSize.height - frameSize.height) >> 1
);
}
}
請注意這個類對於第一個類所作的改動。改動只有類名、frame名、以及GLEventListener名。希望你能夠閱讀代碼中的注釋,否則你會搞不清它要做什麼。
我們實現的GLEventListener確實相對於前面一個例子有了一些改進,它允許我們畫出一些漂亮的圖來。
import net.java.games.jogl.*;
/**
* For our purposes only two of the GLEventListeners matter.
* Those would be init() and display().
* 為了達到我們的目的,GLEventListener中只有兩個方法有用。
* 它們是init()和display()。
*/
public class SecondGLEventListener implements GLEventListener
{
/**
* Take care of initialization here.
* 注意這裡的初始化。
*/
public void init(GLDrawable gld) {
GL gl = gld.getGL();
GLU glu = gld.getGLU();
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
gl.glViewport(0, 0, 500, 300);
gl.glMatrixMode(GL.GL_PROJECTION);
gl.glLoadIdentity();
glu.gluOrtho2D(0.0, 500.0, 0.0, 300.0);
}
/**
* Take care of drawing here.
* 注意這裡的繪圖。
*/
public void display(GLDrawable drawable) {
float red = 0.0f;
float green = 0.0f;
float blue = 0.0f;
GL gl = drawable.getGL();
gl.glClear(GL.GL_COLOR_BUFFER_BIT);
gl.glPointSize(5.0f);
for (int i=0; i<50; i++) {
red -= .09f;
green -= .12f;
blue -= .15f;
if (red < 0.15) red = 1.0f;
if (green < 0.15) green = 1.0f;
if (blue < 0.15) blue = 1.0f;
gl.glColor3f(red, green, blue);
gl.glBegin(GL.GL_POINTS);
gl.glVertex2i((i*10), 150);
gl.glEnd();
}
}
public void reshape(
GLDrawable drawable,
int x,
int y,
int width,
int height
) {}
public void displayChanged(
GLDrawable drawable,
boolean modeChanged,
boolean deviceChanged
) {}
}以上就是我們第一個有趣的JOGL程式。下圖是輸出,有很多好看的顏色。
當 你看到GLEventListener的實現時,可能會感到不知所措。如果你有用C語言編寫OpenGL程式的經驗的話,你也許能猜測出一些東西。如果你 覺得比較茫然,不必擔心,也不要擔心我會讓你記住這些東西,至少現在不必。本書接下來的篇幅將會對這個例子中的 SecondGLEventListener作出解釋。現在,你只需要試著去猜測。試著去修改代碼,產生兩行,或者一行斜的,而不是一行水平線;或是讓所 有的點都變成藍色或紅色。盡情娛樂,這就是你接下來學習JOGL的方式。