JOGLを使ったプログラミングの基礎

前回はNetBeans OpenGL Packを使ってJOGLを使うプログラムの雛型(リスト1 JoglSample.java)を作るところまでを行った。今回はその中身を見ながら、JOGLプログラミングの基本となる部分を紹介していきたい。

リスト1 JoglSample.javaの概観

package apisample;

import com.sun.opengl.util.Animator;
import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCanvas;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;

public class JoglSample implements GLEventListener {
    public static void main(String[] args) { ... }

    public void init(GLAutoDrawable drawable) { ... }

    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { ... }

    public void display(GLAutoDrawable drawable) { ... }

    public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) { ... }
}

JSR-231ではOpenGLのレンダリング対象としてGLDrawableというインタフェースを、コンテキストとしてGLContextというインタフェースを定義している。また、GLDrawableを継承した上でイベントドリブンのメカニズムを持ったインタフェースにGLAutoDrawableがある。GLEventListenerの各メソッドにはこのGLAutoDrawableオブジェクトがイベントとして渡される。

initメソッドで初期化処理

JoglSampleのinitメソッドでは、このGLAutoDrawableからgetGLメソッドを用いてGLオブジェクトを取得している(リスト2)。GLインタフェースはOpenGLの各機能にアクセスするための基本となるインタフェースで、OpenGL APIの関数に対応したメソッドが大量に定義されている。ここではOpenGLのglClearColor関数にあたるglClearColorメソッドで描画領域のクリアを、glShadeModel関数にあたるglShadeModelメソッドでシェーディングモードをスムースシェーディング (GL_SMOOTH) に設定している。

リスト2 JoglSample.init() - 初期化処理

    public void init(GLAutoDrawable drawable) {
        // Use debug pipeline
        // drawable.setGL(new DebugGL(drawable.getGL()));

        GL gl = drawable.getGL();
        System.err.println("INIT GL IS: " + gl.getClass().getName());

        // Enable VSync
        gl.setSwapInterval(1);

        // 描画領域とシェーディングの設定
        gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        gl.glShadeModel(GL.GL_SMOOTH);
    }

reshapeメソッドで描画領域を設定

続いてreshapeメソッドを見てみよう。reshapeメソッドでは描画領域の範囲や投影手法、ビューポートなどを設定する。ここでは最初のglMatrixModeメソッドにGL.GL_PROJECTIONを渡すことで投影に関する行列を指定し、glLoadIdentityメソッドで恒等行列をロードしている。

次のGLUクラスはOpenGL Utility Library(GLU)の機能にアクセスするためのクラスである。gluPerspectiveメソッドは透視投影法の視体積を設定するメソッドで、画面の幅と高さ(第1、第2引数)、前面までの距離(第3引数)、一番奥面までの距離(第4引数)を指定する。

2つ目のglMatrixModeメソッドではGL.GL_MODELVIEWを渡すことでモデルビューの行列を指定し、glLoadIdentityメソッドで恒等行列をロードしている。

リスト3 JoglSample.reshape() - 表示領域が変更された際の処理

    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
        GL gl = drawable.getGL();
        GLU glu = new GLU();

        if (height <= 0) { // avoid a divide by zero error!
            height = 1;
        }
        final float h = (float) width / (float) height;

    // ビューポートを設定
        gl.glViewport(0, 0, width, height);

    // 投影の行列を指定し、描画範囲を設定
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        glu.gluPerspective(45.0f, h, 1.0, 20.0);

    // モデルビューの行列を指定
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();
    }

図形の描画はdisplayメソッドで

実際の図形の描画処理はdisplayメソッドに記述する。基本的にはGLインスタンスに対してOpenGL APIの関数と同名のメソッドを呼び出せばよい。NetBeans OpenGL Packのテンプレートでは、リスト1.3.1のように、あらかじめ三角と四角の2次元図形を描くプログラムが記述されている。まずglTranslatefメソッドでカーソルを初期位置に移動し、glBeginで図形の種類を指定して描画を開始、glColor3fとglVertex3fで色と頂点座標を指定して、glEndで描画を完了するといった具合だ。最後にglFlushメソッドを呼び出してすべての図形の描画を終了する。

なお、JSR 231仕様には載っていないが、JOGLにはGLUT (The OpenGL Utility Toolkit) の実装も用意されているため、これを利用すればより簡単に2Dおよび3Dの図形を作ることが可能だ。

リスト4 JoglSample.display() - 描画処理

    public void display(GLAutoDrawable drawable) {
        GL gl = drawable.getGL();

        // 描画領域をクリア
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
        // 単位行列をロードして行列をリセット
        gl.glLoadIdentity();

        // カーソルを最初の描画位置に移動
        gl.glTranslatef(-1.5f, 0.0f, -6.0f);

        // 三角形を描画を開始
        gl.glBegin(GL.GL_TRIANGLES);
            gl.glColor3f(1.0f, 0.0f, 0.0f);    // 色を赤に設定
            gl.glVertex3f(0.0f, 1.0f, 0.0f);   // 上の頂点
            gl.glColor3f(0.0f, 1.0f, 0.0f);    // 色を緑に設定
            gl.glVertex3f(-1.0f, -1.0f, 0.0f); // 左下の頂点
            gl.glColor3f(0.0f, 0.0f, 1.0f);    // 色を青に設定
            gl.glVertex3f(1.0f, -1.0f, 0.0f);  // 右下の頂点
        // 三角形の描画を終了
        gl.glEnd();

        // カーソルを別の描画位置に移動
        gl.glTranslatef(3.0f, 0.0f, 0.0f);

        // 正方形を描画を開始
        gl.glBegin(GL.GL_QUADS);
            gl.glColor3f(0.5f, 0.5f, 1.0f);    // 色を青に設定
            gl.glVertex3f(-1.0f, 1.0f, 0.0f);  // 左上の頂点
            gl.glVertex3f(1.0f, 1.0f, 0.0f);   // 右上の頂点
            gl.glVertex3f(1.0f, -1.0f, 0.0f);  // 右下の頂点
            gl.glVertex3f(-1.0f, -1.0f, 0.0f); // 左下の頂点
        // 正方形の描画を終了
        gl.glEnd();

        // 全ての描画を終了
        gl.glFlush();
    }

diyplayChangedメソッドはGLAutoDrawableの表示モードや表示デバイスが変更された場合に呼び出されるメソッドだが、今回は必要がないので空白のままになっている。

GLCanvasクラスを使ってSwingプログラムに統合

最後にmainメソッドを見てみよう。GLCanvasというのはGLAutoDrawableインタフェースをimplementsしており、かつjava.awt.Canvasクラスを継承したクラスである。すなわち、OpenGLによる3D図形を描画する機能を持ったAWTコンポーネントということになる。GLCanvasに図形を描画するには、GLEventListenerオブジェクトをaddGLEventListenerメソッドを用いてセットすればよい。つまり今回の例の場合はJoglSampleクラスのインスタンスそのものである。

Animatorクラスはアニメーション機能をサポートするためもの。通常displayメソッドは一度しか呼び出されないが、GLAutoDrawableインスタンスを渡してAnimatorを生成し、startメソッドを呼び出すことで、displayメソッドが周期的に呼ばれるようになる。ウィンドウのクローズ処理では、プログラムを終了する前にアニメーションを正常に停止させるため、Animatorのstopメソッドを呼び出している。

リスト5 JoglSample.main() - 起動時の処理

    public static void main(String[] args) {
        Frame frame = new Frame("Simple JOGL Application");
        // OpenGL用のキャンバスを生成し、リスナーをセット
        GLCanvas canvas = new GLCanvas();
        canvas.addGLEventListener(new JoglSample());

        frame.add(canvas);
        frame.setSize(640, 480);

        // Animatorを生成
        final Animator animator = new Animator(canvas);

        // ウィンドウのクローズ処理
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                // Run this on another thread than the AWT event queue to
                // make sure the call to Animator.stop() completes before
                // exiting
                new Thread(new Runnable() {
                    public void run() {
                        animator.stop();
                        System.exit(0);
                    }
                }).start();
            }
        });

        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

        // アニメーションをスタート
        animator.start();
    }

このプログラムを実行すると図1のように表示される。

図1 JoglSample.javaの実行結果