顯示具有 JOGL 標籤的文章。 顯示所有文章
顯示具有 JOGL 標籤的文章。 顯示所有文章

2014年2月2日 星期日

JOGL Reflection

反射 (Reflection) 是由於光自物體表面反彈所造成的現象,光反彈的方向受到物體表面的幾何結構影響,非常光滑的表面會產生完全相同的反射方向,就像鏡子一般;若是非常的粗糙表面則會產生散射 (Dispersion),自光滑表面所產生的反射稱為高光反射 (Specular Reflection),自粗糙表面所產生的反射稱為散射反射 (Diffuse Reflection),而介於兩者之間則稱為光滑反射 (Glossy Reflection)。非導電材質的反射總是與光源顏色相同,所以所產生的反射是白色,而導電材質則會讓反射的光線帶有顏色,其顏色由入射角度與材質的化學結構決定。

在JOGL中,反射如同在反射的表面上再繪製物體,使其產生反射的效果。可使用javax.media.opengl.GL類別的glStencilFunc()glStencilOp()方法處理。例如:


gl.glColorMask(false, false, false, false);
gl.glDepthMask(false);

gl.glEnable(GL.GL_STENCIL_TEST);
gl.glStencilFunc(GL.GL_ALWAYS, 1, ~0);
gl.glStencilOp(GL.GL_REPLACE, GL.GL_REPLACE, GL.GL_REPLACE);

drawSurface();

gl.glColorMask(true, true, true, true);
gl.glDepthMask(true);

gl.glStencilFunc(GL.GL_EQUAL, 1, ~0);

gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_KEEP);

gl.glPushMatrix();
  gl.glScalef(1.0f, -1.0f, 1.0f);
  gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, g_lightPos, 0);
  drawCube();
gl.glPopMatrix();

gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, g_lightPos, 0);
gl.glEnable(GL.GL_BLEND);
drawSurface();
gl.glDisable(GL.GL_BLEND);

gl.glPushMatrix();
  gl.glDisable(GL.GL_TEXTURE_2D);
  gl.glDisable(GL.GL_LIGHTING);
  gl.glDisable(GL.GL_DEPTH_TEST);
  gl.glEnable(GL.GL_BLEND);
  gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_INCR);
  gl.glColor4f(0.0f, 0.0f, 0.0f, 0.5f);

  gl.glMultMatrixf(g_shadowMatrix, 0);
  drawCube();

  gl.glEnable(GL.GL_TEXTURE_2D);
  gl.glEnable(GL.GL_DEPTH_TEST);
  gl.glDisable(GL.GL_BLEND);
  gl.glEnable(GL.GL_LIGHTING);
gl.glPopMatrix();
gl.glDisable(GL.GL_STENCIL_TEST);

gl.glPushMatrix();
  drawCube();
gl.glPopMatrix();


請參考以下範例。


// 實作GLEventListener介面之方法
// 初始化JOGL
public void init(GLAutoDrawable drawable) {
  gl = drawable.getGL();
  
  // Clear window with color   
  gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

  // set the shading model
  gl.glShadeModel(GL.GL_SMOOTH);

  // set up a single white light
  float lightColor[] = { 1.0f, 1.0f, 1.0f, 1.0f };

  gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, lightColor, 0);
  gl.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, lightColor, 0);

  gl.glEnable(GL.GL_LIGHTING);
  gl.glEnable(GL.GL_LIGHT0);
  gl.glEnable(GL.GL_DEPTH_TEST);

  gl.glEnable(GL.GL_TEXTURE_2D);
  gl.glTexEnvf(GL.GL_TEXTURE_ENV, 
    GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE);

  // load the textures
  cubeTex = loadTexture("opengl.jpg");
  marbleTex = loadTexture("marble.jpg");

  gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);

  float[] plane = { 0.0f, 1.0f, 0.0f, 0.0f };

  // dot product of plane and light position
  float dot = plane[0] * g_lightPos[0] + plane[1] * g_lightPos[1] + 
              plane[1] * g_lightPos[2] + plane[3] * g_lightPos[3];

  // first column
  g_shadowMatrix[0]  = dot  - g_lightPos[0] * plane[0];
  g_shadowMatrix[4]  = 0.0f - g_lightPos[0] * plane[1];
  g_shadowMatrix[8]  = 0.0f - g_lightPos[0] * plane[2];
  g_shadowMatrix[12] = 0.0f - g_lightPos[0] * plane[3];

  // second column
  g_shadowMatrix[1]  = 0.0f - g_lightPos[1] * plane[0];
  g_shadowMatrix[5]  =  dot - g_lightPos[1] * plane[1];
  g_shadowMatrix[9]  = 0.0f - g_lightPos[1] * plane[2];
  g_shadowMatrix[13] = 0.0f - g_lightPos[1] * plane[3];

  // third column
  g_shadowMatrix[2]  = 0.0f - g_lightPos[2] * plane[0];
  g_shadowMatrix[6]  = 0.0f - g_lightPos[2] * plane[1];
  g_shadowMatrix[10] =  dot - g_lightPos[2] * plane[2];
  g_shadowMatrix[14] = 0.0f - g_lightPos[2] * plane[3];

  // fourth column
  g_shadowMatrix[3]  = 0.0f - g_lightPos[3] * plane[0];
  g_shadowMatrix[7]  = 0.0f - g_lightPos[3] * plane[1];
  g_shadowMatrix[11] = 0.0f - g_lightPos[3] * plane[2];
  g_shadowMatrix[15] =  dot - g_lightPos[3] * plane[3];    
}

// 載入圖像以作為貼圖之用
private Texture loadTexture(String filename) {
  com.sun.opengl.util.texture.Texture tex = null;

  // Get current classloader
  ClassLoader cl = this.getClass().getClassLoader();
  
  try {
    // 載入圖像檔案
    tex = com.sun.opengl.util.texture.TextureIO.newTexture(
      cl.getResourceAsStream("images/" + filename), 
      false, TextureIO.JPG);
    // 設定Texture的參數
    tex.setTexParameteri(GL.GL_TEXTURE_WRAP_S, GL.GL_REPEAT);
    tex.setTexParameteri(GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT);
    tex.setTexParameteri(GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
    tex.setTexParameteri(GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
  }
  catch(Exception e) { 
    System.out.println("Error loading texture " + filename);  
  }

  return tex;


// 繪製圖形
public void display(GLAutoDrawable drawable) {
  gl = drawable.getGL();

  gl.glLoadIdentity();
  glu.gluLookAt(0.0, 3.0, 10.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

  // Clear
  gl.glClear(GL.GL_COLOR_BUFFER_BIT | 
    GL.GL_DEPTH_BUFFER_BIT | GL.GL_STENCIL_BUFFER_BIT);

  // rotate the scene
  angle += 3.0;
  gl.glRotated(-angle/8.0, 0.0, 1.0, 0.0);
  gl.glRotated(10.0 * Math.sin(angle/45.0), 1.0, 0.0, 0.0);

  gl.glColorMask(false, false, false, false);
  gl.glDepthMask(false);

  gl.glEnable(GL.GL_STENCIL_TEST);
  gl.glStencilFunc(GL.GL_ALWAYS, 1, ~0);
  gl.glStencilOp(GL.GL_REPLACE, GL.GL_REPLACE, GL.GL_REPLACE);

  drawSurface();

  gl.glColorMask(true, true, true, true);
  gl.glDepthMask(true);
  
  gl.glStencilFunc(GL.GL_EQUAL, 1, ~0);
  
  gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_KEEP);

  gl.glPushMatrix();
    gl.glScalef(1.0f, -1.0f, 1.0f);
    gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, g_lightPos, 0);
    drawCube();
  gl.glPopMatrix();

  gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, g_lightPos, 0);
  gl.glEnable(GL.GL_BLEND);
  drawSurface();
  gl.glDisable(GL.GL_BLEND);

  gl.glPushMatrix();
    gl.glDisable(GL.GL_TEXTURE_2D);
    gl.glDisable(GL.GL_LIGHTING);
    gl.glDisable(GL.GL_DEPTH_TEST);
    gl.glEnable(GL.GL_BLEND);
    gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_INCR);
    gl.glColor4f(0.0f, 0.0f, 0.0f, 0.5f);
  
    gl.glMultMatrixf(g_shadowMatrix, 0);
    drawCube();
  
    gl.glEnable(GL.GL_TEXTURE_2D);
    gl.glEnable(GL.GL_DEPTH_TEST);
    gl.glDisable(GL.GL_BLEND);
    gl.glEnable(GL.GL_LIGHTING);
  gl.glPopMatrix();
  gl.glDisable(GL.GL_STENCIL_TEST);

  gl.glPushMatrix();
    drawCube();
  gl.glPopMatrix();
}

private void drawSurface(){
  float[] surfaceColor = { 1.0f, 1.0f, 1.0f, 0.6f };
  gl.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, surfaceColor, 0);
  gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT_AND_DIFFUSE, 
    surfaceColor, 0);
  gl.glMaterialf(GL.GL_FRONT, GL.GL_SHININESS, 200.0f);

  // 啟用貼圖
  cubeTex.enable();
  marbleTex.enable();
  // 繫結貼圖
  marbleTex.bind();

  gl.glBegin(GL.GL_QUADS);
  gl.glNormal3f(0.0f, 1.0f, 0.0f);

  float x = -5.0f;
  float z = -5.0f;

  for (int i = 0; i < 10; i++, x += 1.0f) {
    for (int j = 0; j < 10; j++, z += 1.0f) {
      gl.glTexCoord2f(0.0f, 0.0f);
      gl.glVertex3f(x, 0.0f, z);
      gl.glTexCoord2f(1.0f, 0.0f);
      gl.glVertex3f(x + 1.0f, 0.0f, z);
      gl.glTexCoord2f(1.0f, 1.0f);
      gl.glVertex3f(x + 1.0f, 0.0f, z + 1.0f);
      gl.glTexCoord2f(0.0f, 1.0f);
      gl.glVertex3f(x, 0.0f, z + 1.0f);
    }
    z = -5.0f;
  }

  gl.glEnd();

  marbleTex.disable();  



【執行結果】


【參考資料】

[1] Java Platform, Standard Edition 7, API Specification.
[2] JOGL.
[3] 黃嘉輝,完全探索Java遊戲程式設計,上奇資訊。


© Chia-Hui Huang

JOGL Animation

在JOGL中,動畫的環境由com.sun.opengl.util.Animator類別處理,其建構函式為:

  public Animator()
  public Animator(GLAutoDrawable drawable)

其中參數drawableGLAutoDrawable介面之物件,可GLCanvasGLJPanel類別建立、或以GLAutoDrawable介面的getGL()方法建立。

Animator物件建立之後,可使用以下方法處理動畫:
  • start()開始處理動畫。
  • stop()停止處理動畫。
  • isAnimating()判斷是否正在執行動畫處理。
以Java Swing為例:


// Set OpenGL Capabilities
GLCapabilities glcapabilities = new GLCapabilities();
// Set Drawable Factory
GLDrawableFactory gldrawablefactory =
  GLDrawableFactory.getFactory();
GLDrawable gldrawable = gldrawablefactory.getGLDrawable(
  this, glcapabilities, null);
// Set Double Buffer
glcapabilities.setDoubleBuffered(true);

// 建立GLJPanel物件 
GLJPanel glpanel = new GLJPanel(glcapabilities);
// 註冊GLEventListener
glpanel.addGLEventListener(this);
...

// 建立Animator物件
Animator animator = new Animator(glpanel);
glpanel.requestFocusInWindow();
// 開始動畫處理
animator.start();
...


當以start()方法開始處理動畫時,則整個GLJPanel物件將處於動畫的狀態,但如何處理動畫,仍需自行設計。

請參考以下範例,以Animator類別處理動畫環境,此外並使用以下的方法:
  • glLoadIdentity():載入單位矩陣 (Identity Matrix)。
  • glMaterialfv():設定物體的材質 (Material)。
  • glMatrixMode():設定座標系統。
  • glPopMatrix():自堆疊最上層移除矩陣。
  • glPushMatrix():複製目前矩陣至堆疊最上層。
  • gluNewQuadric():建立二次曲面 (Quadric)。
  • gluQuadricOrientation():設定二次曲面的方向 (Orientation)。
  • glutWireSphere():繪製線框球體 (Wireframe Sphere)。


// 實作GLEventListener介面之方法
// 初始化JOGL
public void init(GLAutoDrawable drawable) {
  GL gl = drawable.getGL();
  gl.setSwapInterval(1);

  float white[] = {1.0f, 1.0f, 1.0f, 1.0f};

  gl.glEnable(GL.GL_CULL_FACE);
  // 啟用光源 
  gl.glEnable(GL.GL_LIGHTING);
  // 啟用光源0 
  gl.glEnable(GL.GL_LIGHT0);
  gl.glEnable(GL.GL_DEPTH_TEST);

  /* make the Ball */
  ball = gl.glGenLists(1);

  GLUquadric quad;
  quad = glu.gluNewQuadric();
  glu.gluQuadricOrientation(quad, GLU.GLU_OUTSIDE);
  glu.gluQuadricNormals    (quad, GLU.GLU_SMOOTH);
  glu.gluQuadricTexture    (quad, true);

  gl.glNewList(ball, GL.GL_COMPILE);
  gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT_AND_DIFFUSE, white, 0);

  glut.glutWireSphere(5, 25, 25);
  gl.glEndList();
          
  gl.glEnable(GL.GL_NORMALIZE);
}

// 繪製圖形
public void display(GLAutoDrawable drawable) {
  angle += 100.0f;

  GL gl = drawable.getGL();
  
  if ((drawable instanceof GLJPanel) &&
      !((GLJPanel) drawable).isOpaque() &&
      ((GLJPanel) drawable).shouldPreserveColorBufferIfTranslucent()) {

    // 以目前之顏色清除視窗
    gl.glClear(GL.GL_DEPTH_BUFFER_BIT);
  } 
  else {
    // 以目前之顏色清除視窗
    gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
  }
          
  // 複製目前之矩陣至堆疊最上層
  gl.glPushMatrix();
  // 沿x座標旋轉指定角度
  gl.glRotatef(view_rotx, 1.0f, 0.0f, 0.0f);
  // 沿y座標旋轉指定角度
  gl.glRotatef(view_roty, 0.0f, 1.0f, 0.0f);
  // 沿z座標旋轉指定角度
  gl.glRotatef(view_rotz, 0.0f, 0.0f, 1.0f);
          
  // 沿x,y,z座標旋轉指定角度
  gl.glRotatef(angle, 0.0f, 0.0f, 1.0f);
  gl.glCallList(ball);
  // 自堆疊最上層移除矩陣
  gl.glPopMatrix();
  // 即刻執行JOGL指令 
  gl.glFlush();
}

// 當視窗大小改變時
public void reshape(GLAutoDrawable drawable, 
  int x, int y, int width, int height) {
  
  GL gl = drawable.getGL();

  float h = (float)height / (float)width;
          
  // 設定座標系統
  gl.glMatrixMode(GL.GL_PROJECTION);

  gl.glLoadIdentity();
  gl.glFrustum(-1.0f, 1.0f, -h, h, 5.0f, 60.0f);
  // 設定座標系統
  gl.glMatrixMode(GL.GL_MODELVIEW);
  gl.glLoadIdentity();
  gl.glTranslatef(0.0f, 0.0f, -40.0f);
}


【執行結果】


【參考資料】

[1] Java Platform, Standard Edition 7, API Specification.
[2] JOGL.
[3] 黃嘉輝,完全探索Java遊戲程式設計,上奇資訊。

© Chia-Hui Huang


2014年2月1日 星期六

NASA World Wind Java SDK

NASA World Wind Java SDK是由NASA (National Aeronautics and Space Administration, 美國太空總署) 以JOGL為基礎所開發支援類似Google Earth的進階套件,其API支援包括地球、火星、月亮等星球表面之圖片下載,並支援星球地形處理 (Terrain)、以平面方式呈現星球表面 (Flat World)、藍色大理石紋裡表面 (Blue Marble Surface)、地震分析 (Earthquake) 等特殊功能。

由於以NASA World Wind Java SDK所開發之程式,實際上是即時自NASA網站下載相關星球圖片,因此必須具有網路連線。此外,若以Java Applet方式處理,由於安全性的限制,Java Applet僅能與其置放的伺服器主機建立連線,不能與其他的主機如NASA建立連線,因此將產生Security例外錯誤,有關的解決方法,請參考Java Security Policy之說明。

NASA World Wind Java SDK是以JOGL為基礎所開發,因此其程式架構與JOGL類似。

在JOGL中,以GLCanvasGLJPanel類別建立JOGL繪圖物件,而在NASA World Wind Java SDK中,則是以WorldWindowGLCanvas類別或WorldWindowGLJPanel類別建立NASA World Wind繪圖物件以處理3D繪圖,前者繼承自GLCanvas類別,以支援Java AWT之繪圖,後者則繼承自GLJPanel類別,以支援Java Swing之繪圖。

WorldWindowGLCanvasWorldWindowGLJPanel類別所建立的繪圖物件,只是一個顯示NASA圖片的環境,而星球表面的圖片則是以層 (Layer) 的方式一層一層顯示,其層別可由以下之類別處理,皆繼承自gov.nasa.worldwind.layers.AbstractLayer抽象類別:
  • gov.nasa.worldwind.layers.CompassLayer
  • gov.nasa.worldwind.layers.Earth.BMNGSurfaceLayer
  • gov.nasa.worldwind.layers.Earth.EarthNASAPlaceNameLayer
  • gov.nasa.worldwind.layers.Earth.LandsatI3
  • gov.nasa.worldwind.layers.Earth.USGSDigitalOrtho
  • gov.nasa.worldwind.layers.Earth.USGSUrbanAreaOrtho
此外,以下為NASA World Wind Java SDK的各類層別,其中Earth、Mars、Moon分別代表地球、火星、月亮之表面圖片的層別:
  • gov.nasa.worldwind.layers.AnnotationLayer
  • gov.nasa.worldwind.layers.CrosshairLayer
  • gov.nasa.worldwind.layers.ScalebarLayer
  • gov.nasa.worldwind.layers.SkyColorLayer
  • gov.nasa.worldwind.layers.SkyGradientLayer
  • gov.nasa.worldwind.layers.StarsLayer
  • gov.nasa.worldwind.layers.TerrainProfileLayer
  • gov.nasa.worldwind.layers.TrackMarkerLayer
  • gov.nasa.worldwind.layers.TrackPipesLayer
  • gov.nasa.worldwind.layers.WorldMapLayer
  • gov.nasa.worldwind.layers.Earth.BMNGWMSLayer
  • gov.nasa.worldwind.layers.Earth.CountryBoundariesLayer
  • gov.nasa.worldwind.layers.Earth.LandsatI3WMSLayer
  • gov.nasa.worldwind.layers.Earth.MGRSGraticuleLayer
  • gov.nasa.worldwind.layers.Earth.NASAWFSPlaceNameLayer
  • gov.nasa.worldwind.layers.Earth.OpenStreetMapLayer
  • gov.nasa.worldwind.layers.Earth.UTMGraticuleLayer
  • gov.nasa.worldwind.layers.Mars.MOCColorizedBaseLayer
  • gov.nasa.worldwind.layers.Mars.MOCColorizedLayer
  • gov.nasa.worldwind.layers.Mars.MOCLayer
  • gov.nasa.worldwind.layers.Mars.MolaColorASULayer
  • gov.nasa.worldwind.layers.Mars.MolaColoredJPLLayer
  • gov.nasa.worldwind.layers.Mars.THEMISColorLayer
  • gov.nasa.worldwind.layers.Mars.THEMISLayer
  • gov.nasa.worldwind.layers.Moon.Clementine30Layer
  • gov.nasa.worldwind.layers.Moon.Clementine40BaseLayer
  • gov.nasa.worldwind.layers.Moon.Clementine40Layer
  • gov.nasa.worldwind.layers.Moon.ShadedElevationLayer
  • gov.nasa.worldwind.layers.placename.PlaceNameLayer
層別類別之特殊在於可藉由動作 (Action) 之定義,處理其相對的事件,其程式架構如下:


public JOGLEarth.LayerAction[] layers = new JOGLEarth.LayerAction[] {
  new JOGLEarth.LayerAction(new MNGSurfaceLayer(), true),
  new JOGLEarth.LayerAction(new CompassLayer(), true),
  new JOGLEarth.LayerAction(new EarthNASAPlaceNameLayer(), true),
  new JOGLEarth.LayerAction(new LandsatI3(), true),
  new JOGLEarth.LayerAction(new USGSDigitalOrtho(), true),
  new JOGLEarth.LayerAction(new USGSUrbanAreaOrtho(), true),
};
...
LayerList layerList = new LayerList();

for (JOGLEarth.LayerAction action : layers) {
  JCheckBox jcb = new JCheckBox(action);
  jcb.setFont(new Font("dialog", Font.PLAIN, 10));
  
  jcb.setSelected(action.selected);
  layerPanel.add(jcb);
  layerList.add(action.layer);

  if (action.layer instanceof TiledImageLayer)
    ((TiledImageLayer) action.layer).setShowImageTileOutlines(false);

  if (action.layer instanceof LandsatI3)
    ((TiledImageLayer) action.layer).setDrawBoundingVolumes(false);

  if (action.layer instanceof USGSDigitalOrtho)
    ((TiledImageLayer) action.layer).setDrawTileIDs(false);
}
...
private static class LayerAction extends AbstractAction {
  private Layer layer;
  private boolean selected;

  public LayerAction(Layer layer, boolean selected) {
    super(layer.getName());
    this.layer = layer;
    this.selected = selected;
    this.layer.setEnabled(this.selected);
  }

  public void actionPerformed(ActionEvent actionEvent) {
    if (((JCheckBox) actionEvent.getSource()).isSelected())
      this.layer.setEnabled(true);
    else
      this.layer.setEnabled(false);

    Thread thread = new Thread() {
      public void run() {
        wwGLJPanel.repaint();
      }
    };
    thread.start();
  }
}


待各層別設定之後,則需將層別加入之前所建立的WorldWindowGLCanvasWorldWindowGLJPanel繪圖物件之中,以顯示星球表面各層別的圖片。

由於WorldWindowGLCanvasWorldWindowGLJPanel類別支援MVC架構,其Model部份可由Model介面或以WorldWind類別的createConfigurationComponent()方法建立。待MVC Model建立之後,則以Model介面的setLayers()方法將層別加入繪圖物件之中,並以WorldWindowGLCanvasWorldWindowGLJPanel類別的setModel()方法設定NASA World Wind繪圖物件之MVC Model,例如:


// 建立WorldWindowGLCanvas類別的MVC Model
gov.nasa.worldwind.Model wwModel = (Model)
  WorldWind.createConfigurationComponent(
    "gov.nasa.worldwind.avkey.ModelClassName";

// 將層別加入繪圖物件之中
wwModel.setLayers(layerList);
...
// 設定NASA World Wind繪圖物件之MVC Model
wwGLJPanel.setModel(wwModel);


【執行結果】


【參考資料】

[1] Java Platform, Standard Edition 7, API Specification.
[2] NASA World Wind Java SDK:http://worldwind.arc.nasa.gov/java/
[3] 黃嘉輝,完全探索網路程式設計 - 使用Java,上奇資訊。

© Chia-Hui Huang


Java Bindings for OpenGL

處理3D之API較著名的有OpenGL、PHIGS、DirectX等,以Java開發,則可使用Java 3D API。此外,針對處理3D繪圖及音效,Java另有Java Bindings for OpenGL、Java Bindings for OpenAL、Lightweight Java Game Library、Processing、AgentFX、Xith3D、jME、jgeom、jPCT等API。

在Java中,欲處理3D可使用Java 3D API,除此之外,對於習慣OpenGL的開發者而言,Java Bindings for OpenGL (JOGL) 則是另一個不錯的選擇。

OpenGL其前身為IRIS GL,IRIS GL是Silicon Graphics Inc. (SGI) 為改進另一3D公開標準PHIGS (Programmer's Hierarchical Interactive Graphics System) 所開發的API,但由於SGI在開發IRIS GL時,其部份API並非與3D有關,而是與非標準之視窗、鍵盤及滑鼠有關,且由於牽涉到專利權的關係,因此SGI並未將IRIS GL定位為開放標準。至1992年,SGI以IRIS GL為基礎,宣佈成立OpenGL Architecture Review Board (OpenGL ARB),以開發及訂定OpenGL規格書。1995年,由於Microsoft發表Direct3D成為SGI OpenGL的最大競爭對手,此舉對SGI造成不少威脅,因此雙方於1997達成協議,試圖於兩者間建立標準的介面,但此計劃後來亦不了了之。

基本上,JOGL並非以Java重新開發OpenGL (雖然部份OpenGL的方法確實以Java改寫),而是在Java與OpenGL之間擔任中間銜接的角色,如同其名稱Binding一般,以下為各類程式語言與OpenGL間之銜接函式庫,JOGL則為其中之一:
  • Delphi: Delphi OpenGL Toolkit (DOT).
  • Fortran: Fortran 90 Bindings for OpenGL (F90GL).
  • Java: Java Bindings for OpenGL (JOGL).
  • Lightweight Java Game Library (LWJGL).
  • Perl: Perl OpenGL (POGL).
  • Python: Python OpenGL Bindings (PyOpenGL).
  • Ruby: ruby-opengl.
  • Visual Basic: OpenGL 1.1 ActiveX Control.
以Microsoft Windows作業系統 (586 CPU) 為例,待下載JOGL ZIP檔案及解壓縮之後,可在其lib目錄找到以下執行JOGL所需的檔案,其中GlueGen為透過JNI (Java Native Interface) 建立JOGL及JOAL (Java Bindings for OpenAL) 與OpenGL及OpenAL函式庫間之介面:
  • jogl.jar
  • jogl.dll
  • jogl_awt.dll
  • jogl_cg.dll
  • gluegen-rt.jar
  • gluegen-rt.dll
以JDK 7.0及Microsoft Windows作業系統為例,將上述檔案複製至C:\jdk1.7.0\jre\lib\ext目錄下,並需設定CLASSPATH、PATH與java.library.path環境變數,分別設定上述Java Archive檔案、DLL檔案與其所在之目錄,例如:
  • JAVA_EXT=C:\jdk1.7.0\jre\lib\ext
  • CLASSPATH=%CLASSPATH%;%JAVA_EXT%\jogl.jar;%JAVA_EXT%\gluegen-rt.jar
  • PATH=%PATH%;%JAVA_EXT%\jogl.dll;%JAVA_EXT%\gluegen-rt.dll
  • java.library.path=%JAVA_EXT%
至於JOGL API說明文件,除了標準的JOGL API文件之外,在使用JOGL時,可同時參考OpenGL規格書及使用手冊。OpenGL規格書可自http://www.opengl.org/documentation/specs/下載,此外以下為筆者常參考的使用手冊,其中第一、二、三本分別俗稱Red Book、Blue Book與Orange Book:
  • OpenGL Architecture Review Board, Dave Shreiner, Mason Woo, Jackie Neider, “OpenGL Programming Guide: The Official Guide to Learning OpenGL, Version 2 (5th Edition)”, Addison-Wesley Professional, 2005.
  • OpenGL Architecture Review Board, Dave Shreiner, “OpenGL Reference Manual: The Official Reference Document to OpenGL, Version 1.4 (4th Edition)”, Addison-Wesley Professional, 2004.
  • Randi J. Rost, "OpenGL Shading Language (2nd Edition)”, Addison-Wesley Professional, 2006.
  • Richard S. Wright, Benjamin Lipchak, "OpenGL SuperBible (3rd Edition)”, SAMS, 2004.
  • Tom McReynolds, David Blythe, "Advanced Graphics Programming Using OpenGL”, Morgan Kaufmann, 2005.
  • Dave Astle, Kevin Hawkins, "Beginning OpenGL Game Programming", Course Technology PTR, 2004.
  • Kevin Hawkins, Dave Astle, Andre LaMothe, "OpenGL Game Programming", Course Technology PTR, 2002.
  • Dave Astle, "More OpenGL Game Programming", Course Technology PTR, 2005.
JOGL的主要套件包括:
  • javax.media.opengl
  • javax.media.opengl.glu
  • com.sun.opengl.cg
  • com.sun.opengl.util
  • com.sun.opengl.util.j2d
  • com.sun.opengl.util.texture
  • com.sun.opengl.util.texture.spi
其中,javax.media.opengl套件包括所有OpenGL 2.0的方法,並支援Java AWT與Java Swing繪圖及事件之API。javax.media.opengl.glu套件為支援OpenGL Graphics System Utility (GLU) 之API套件。com.sun.opengl.util套件為支援OpenGL Utility Toolkit (GLUT) 之API套件,GLUT此公用函式庫提供OpenGL 2.0額外繪製3D幾何圖形與動畫等功能。

在介紹JOGL的程式架構之前,首先介紹如何以C語言開發OpenGL程式,藉此比較C與Java兩者間之差異,請參考以下範例:


/* 以C語言開發OpenGL程式 */
/* DrawRect.c         */

#include <windows.h>
#include <gl/glut.h>

/* 主程式 */
void main(void) {
  /* 設定顯示模式 */
  glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
  /* 建立視窗 */
  glutCreateWindow("DrawRect");
  /* 定義繪圖函式為自訂之RenderScene */
  glutDisplayFunc(RenderScene);
  /* 定義當視窗大小改變時所呼叫之函式 */
  glutReshapeFunc(ChangeSize);
  /* 自訂函式以初始化環境 */
  Initialize();
  /* 進入OpenGL迴圈 */
  glutMainLoop();
}

/* 初始化環境 */
void Initialize(void) {
  /* 以指定顏色清除背景 */
  glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
}

/* 繪圖 */
void RenderScene(void) {
  /* 以目前之顏色清除視窗 */
  glClear(GL_COLOR_BUFFER_BIT);
  /* 設定目前繪圖顏色為紅色 */
  glColor3f(1.0f, 0.0f, 0.0f);
  /* 繪製矩形並以目前顏色填滿面積 */
  glRectf(100.0f, 150.0f, 150.0f, 100.0f);
  /* 即刻執行OpenGL指令 */
  glFlush();
}

/* 當視窗大小改變時 */
void ChangeSize(GLsizei w, GLsizei h) {
  /* 設定視界大小為視窗之大小 */
  glViewport(0, 0, w, h);
  /* 重設座標系統*/
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  if (w <= h) 
    glOrtho (0.0f, 250.0f, 0.0f, 250.0f*h/w, 1.0, -1.0);
  else 
    glOrtho (0.0f, 250.0f*w/h, 0.0f, 250.0f, 1.0, -1.0);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}


以C語言開發OpenGL程式,其中較重要的方法為:
  • glutDisplayFunc():設定自訂的繪圖函式,為GLUT方法。
  • glutReshapeFunc():設定當視窗大小改變時所呼叫的函式,為GLUT方法。
  • glutMainLoop():進入OpenGL迴圈以偵測OpenGL相關狀態的改變,為GLUT方法。
在JOGL中,則是將上述的方法包裝成為javax.media.opengl.GLEventListener介面,如同Java Event Listener一般,其方法包括:
  • init(GLAutoDrawable drawable)初始化JOGL,可在此方法中定義JOGL之環境,如設定光源位置 (Light Position)、透視度 (Perspective) 等、或載入貼圖所需之圖像檔案,init()方法會在初次建立OpenGL Context之後執行,如同上述範例的Initialize()自訂函式般。
  • display(GLAutoDrawable drawable)如同上述範例的glutDisplayFunc()方法中所定義的RenderScene()函式般,用以繪製圖形。
  • reshape(GLAutoDrawable drawable, int x, int y, int width, int height)如同上述範例的glutReshapeFunc()方法中所定義之ChangeSize()函式般,當視窗大小改變時所呼叫的方法,通常在此方法中重設透視度 (Perspective) 及視界 (Viewport)。
  • displayChanged(GLAutoDrawable drawable, boolean mode, boolean device)當顯示模式 (Display Mode) 或顯示裝置 (Display Device) 改變時所呼叫的方法,例如由16位元改為32位元顯示模式、或在支援虛擬多螢幕的顯示裝置中由一個螢幕移至另一螢幕。因此若無前述之改變,通常不需實作此方法之內容。
因此在Java中,可透過實作javax.media.opengl.GLEventListener介面及其所提供的方法設計JOGL程式。欲註冊GLEventListener介面,可使用GLCanvasGLJPanel類別的addGLEventListener()方法建立其事件Listener,兩者之差別在於前者繼承自java.awt.Canvas類別,以支援Java AWT之繪圖,後者則繼承自javax.swing.JPanel類別,以支援Java Swing之繪圖。

GLCanvas類別實作GLEventListener介面的程式架構大致如下:


import java.awt.*;
import java.awt.event.*;

// JOGL
import javax.media.opengl.*;
import javax.media.opengl.glu.*;
import com.sun.opengl.util.*;

public class JOGLDemo extends Frame implements GLEventListener {
  ...
  // 建構函式
  public JOGLDemo(){
    ...
    // 建立GLCanvas物件 
    GLCanvas canvas = new GLCanvas();

    // 註冊GLEventListener
    canvas.addGLEventListener(this);

    // 將GLCanvas物件加入Frame中
    this.add(canvas);
    ...
  }

  // 實作GLEventListener介面的方法
  // 初始化JOGL
  public void init(GLAutoDrawable drawable){...}

  // 繪製圖形
  public void display(GLAutoDrawable drawable){...}

  // 當視窗大小改變時
  public void reshape(GLAutoDrawable drawable,
    int x, int y, int width, int height){...}

  // 當顯示模式或裝置改變時
  public void displayChanged(GLAutoDrawable drawable, 
    boolean modeChanged, boolean deviceChanged){...}
}


GLJPanel類別實作GLEventListener介面的程式架構大致如下:


import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

// JOGL
import javax.media.opengl.*;
import javax.media.opengl.glu.*;
import com.sun.opengl.util.*;

public class JOGLDemo extends JFrame implements GLEventListener {
  ...
  // 建構函式
  public JOGLDemo(){
    ...
    // 建立GLJPanel物件
    GLJPanel glpanel = new GLJPanel();

    // 註冊GLEventListener
    glpanel.addGLEventListener(this);

    // 將GLJPanel物件加入JFrame中
    this.add(glpanel);
    ...
  }

  // 實作GLEventListener介面的方法
  // 初始化JOGL
  public void init(GLAutoDrawable drawable){...}

  // 繪製圖形
  public void display(GLAutoDrawable drawable){...}

  // 當視窗大小改變時
  public void reshape(GLAutoDrawable drawable,
    int x, int y, int width, int height){...}

  // 當顯示模式或裝置改變時
  public void displayChanged(GLAutoDrawable drawable, 
    boolean modeChanged, boolean deviceChanged){...}
}


【執行結果】


【參考資料】

[1] Java Platform, Standard Edition 7, API Specification.
[2] Lance Williams, "Pyramidal Parametrics", Computer Graphics, Vol. 17 No.3, July 1983.
[3] Randi J. Rost, "OpenGL Shading Language (2nd Edition)", Addison-Wesley, 2006.
[4] Richard S. Wright, Benjamin Lipchak, "OpenGL SuperBible (3rd Edition)", SAMS, 2004.
[5] Delphi OpenGL Toolkit.
[6] Fortran 90 Bindings for OpenGL.
[7] GlueGen.
[8] JOAL.
[9] JOGL.
[10] LWJGL.
[11] OpenGL.
[12] Perl OpenGL.
[13] Python OpenGL Bindings.
[14] ruby-opengl.
[15] 黃嘉輝,完全探索網路程式設計 - 使用Java,上奇資訊。

© Chia-Hui Huang