今週から,Java bindings for OpenGL(JOGL)を使ったOpenGLのプログラミングに挑戦していきましょう。はやる気持ちはおさえて,今週は単に立方体を描画するところまでを行います。来週は,描画した立方体に対して,回転や移動などを加えていきます。

JOGLを使用したプログラミングは,基本的にイベントドリブンです。JOGLのイベントを扱うリスナーとしてjavax.media.opengl.GLEventListenerインタフェースが定義されています。

GLEventListenerインタフェースには次の4種類のメソッドがあります。

メソッド 説明
init 初期化時にコールされるメソッド
reshape 表示領域が変更されたときにコールされるメソッド
display 描画を行うときにコールされるメソッド
displayChanged 表示の切り替えが発生した場合にコールされるメソッド

initメソッドは起動時に1度だけコールされ,reshapeはフレームのサイズを変更したときなどにコールされます。描画を行うにはdisplayメソッドに記述します。

GLEventListenerインタフェースをインプリメントしたクラスは,javax.media.opengl.GLAutoDrawableインタフェースをインプリメントしたクラスにaddGLEventListenerメソッドを使用して登録します。

GLAutoDrawableインタフェースをインプリメントしたクラスとして,AWTで使用できるjavax.media.opengl.GLCanvasクラス,Swingで使用できるGLJPanelクラスがあります。

サンプルでは立方体を描画させるので,SimpleCubeクラスとしましょう。ここまでの説明した部分を記述すると次のようになります。AWTを使用するので,GLCanvasクラスを使用しました。

import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
 
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLCanvas;
 
public class SimpleCube implements GLEventListener {
 
    public SimpleCube() {
        Frame frame = new Frame("Simple Cube");
 
        // 3Dを描画するコンポーネント
        GLCanvas canvas = new GLCanvas();
        canvas.addGLEventListener(this);
  
        frame.add(canvas);
        frame.setSize(300, 300);
 
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
 
        frame.setVisible(true);
    }
 
    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) {}
 
    public static void main(String[] args) {
        new SimpleCube();
    }
}

このコードをコンパイルして,実行してみましょう。

C:\temp>javac SimpleCube.java
 
C:\temp>java SimpleCube

正しくソースが記述されていれば,実行すると真っ黒で何も描画されていないウィンドウが表示されるはずです。まだ,描画処理を何も記述していないので,真っ黒でいいのです。

さて,ここから描画に関する部分を記述していきます。

完成版はこちらからダウンロードできます SimpleCube.java

描画のためのメソッドはほとんどjavax.media.opengl.GLクラスで定義されています。このクラスはOpenGLのメソッドをそのままJavaでラップしたクラスです。

まず,GLオブジェクトを得なければなりません。GLオブジェクトはGLAutoDrawableオブジェクトから取得できます。

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

GLオブジェクトはこの後,何度も使用するのでフィールドで定義しました。

OpenGLにはユーティリティ用のツールキットGLUTというものがあります。JOGLでもGLUTを使用することができ,com.sun.opengl.util.GLUTクラスで表されます。GLUTは普通のクラスなので,そのままnewで生成できます。

    private GL gl;
    private GLUT glut;
		
    public void init(GLAutoDrawable drawable) {
        gl = drawable.getGL();
        glut = new GLUT();
    }

reshapeメソッドには描画する範囲や,どこから見るかということなどを記述します。この記述に関する説明は来月以降に行うので,今のところはこういうものだと思っていてください。

    public void reshape(GLAutoDrawable drawable,
                        int x, int y,
                        int width, int height) {
        float ratio = (float)height / (float)width;
        
        gl.glViewport(0, 0, width, height);
 
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        gl.glFrustum(-1.0f, 1.0f, -ratio, ratio,
		             5.0f, 40.0f);
 
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();
        gl.glTranslatef(0.0f, 0.0f, -20.0f);
    }

やっと,描画処理を記述するところまできました。描画はdisplayメソッドに記述します。

    public void display(GLAutoDrawable drawable) {
        gl.glClear(GL.GL_COLOR_BUFFER_BIT);
 
        // 大きさ 2 の線画の立方体を描画
        glut.glutWireCube(2.0f);
    }
SimpleCubeの実行結果
図1 SimpleCubeの実行結果
glutWireTeapot
図2 glutWireTeapot

「えっ,これだけ」という感じですね。まず,画面のクリアを行うためにGL#glClearメソッドをコールします。glClearメソッドの引数はこういうものだと思っていてください注1

次に立方体を描画するのですが,これにはGLUTクラスのメソッドを使用することができます。glutWireCubeメソッドがこれに当たります。

引数の2.0fは立方体の1辺の長さを示しています。

メソッド名にWireが含まれていることからわかると思いますが,描画の結果は線画になります。線画にしないときは,WireをSolidに変えたglutSolidCubeメソッドを使用します。

なぜ線画なのか,それは実行してみればわかります。図1の実行結果を見ると,四角形が入れ子になったような図が描画されています。

これは,遠近法により,遠くにあるものが小さく見えるため,入れ子になったように見えるのです。

まだ,色に関する処理や,回転などの処理を記述していないので,glutSolidCubeメソッドを実行すると塗りつぶされた四角形が描画されるます。それだと,全く3Dに見えないので,あえて線画にしたわけです。

GLUTには立方体以外にも以下のような立体を描画するメソッドが用意されています(カッコの中がメソッド名)。

  • 4面体(glutSolidTetrahedron/glutWireTetrahedron)
  • 8面体(glutSolidOctahedron/glutWireOctahedron)
  • 12面体(glutSolidDodecahedron/glutWireDodecahedron)
  • 20面体(glutSolidIcosahedron/glutWireIcosahedron)
  • 円錐(glutSolidCone/glutWireCone)
  • 球(glutSolidSphere/glutWireSphere)
  • ドーナツ型(glutSolidTorus/glutWireTorus)
  • ティーポット(glutSolidTeapot/glutWireTeapot)

図2にglutWireCubeメソッドをコールしている部分をglutWireTeapotメソッドに変更したものを示しました。このティーポットはユタ大学で30年以上も前にモデリングされたもので,ユタティーポットと呼ばれているものです(Wikipediaによる解説)。

今回のサンプルだと平面に描いたものと全然変わりありません。しかし,回転などの変換や,光などを加えていくことによって,3D的な表情を持たせることが可能なのです。

注1 OpenGLにはここで使用したような定数が山ほどあります。すべてを覚えるのは大変なので(筆者もすべては把握していません),必要な時に覚えていくぐらいのスタンスでかまわないと思います。

著者紹介 櫻庭祐一

横河電機の研究部門に勤務。同氏のJavaプログラマ向け情報ページ「Java in the Box」はあまりに有名