SwingでのJOGLの利用(Java SE 5.0以前)
図1 SwingでのJOGLの利用(Java SE 5.0以前)
SwingでのJOGLの利用(Java SE 6)
図2 SwingでのJOGLの利用(Java SE 6)

先週はJOGLを使って,3DのAWTコンポーネントを作ってみました。今週は3DのSwingコンポーネントを作っていきます。

しかし,SwingでJOGLを使うには問題があります。

SwingとAWTを比較したときに,一番異なることは何でしょう?

AWTは描画をウィンドウ・システムに依存しています。例えば,Buttonクラスはウィンドウ・システムで使用できるボタンをそのまま使っています。つまり,ボタンの描画はウィンドウ・システムで行っており,Javaは描画を依頼するだけです。

AWTとウィンドウ・システムの間で,対応するコンポーネントを仲介しているのがpeerです。例えばButtonクラスには,対応するpeerとしてButtonPeerインタフェースが定義されており,WindowsであればWButtonPeerクラス,Linux/SolarisであればXButtonPeerクラスが使われます。

Buttonクラスのようにpeerがあるクラスは,重量級コンポーネント(Heavyweight Component)と呼ばれます。

一方のSwingはどうでしょう。

SwingはAWTとは異なり,描画をすべて自前で行っています。javax.swing.plafパッケージに描画を行うためのUIクラスが定義されており,Look&Feelによって,UIから派生させたクラスを使用します。

例えば,JButtonクラスの場合,UIはButtonUIクラスです。ButtonUIクラスはアブストラクト・クラスで,MetalButtonUIクラスやSynthButtonUIクラスなどの派生クラスが定義されています。AWTのようにpeerがないので,軽量級コンポーネント(Lightweight Component)と呼ばれています。

この手法には,ウィンドウ・システムに依存せずにどのようなプラットフォームでも同じユーザー・インタフェースを実現できるというメリットがあります。

ところが,OpenGLを使用する場合にはこれがデメリットになってしまいます。

OpenGLはウィンドウ・システムと密接に結びついており,そのままではSwingではJOGLを使用できません。

そこで,まずJOGLがオフスクリーン・バッファに対して描画を行います。それをバイトの配列に変換し,改めてJava 2Dを使用して描画するという手順を採っています(図1)。こんな手間をかけているので,パフォーマンスには目をつぶらざるを得ませんでした。

しかし,Java 2Dが最終的に描画するのはOpenGLに対してです(WindowsではDirectXですが)。とすると,Java 2DからOpenGLに至る部分にアクセスできれば,このような手間のかかることを行わなくても済みます。

そこで登場するのがJava SE 6です。Java SE 6ではJava 2DとOpenGL周りが整備され,Java2Dが使用するOpenGLのパイプラインに外部からアクセスできるようになりました。つまり,Java SE 6を使えば,JOGLが軽量級コンポーネントとして実装されていても,直接OpenGLに対して描画できるようになったのです(図2)。

試しにJOGLのデモで試してみましょう。

JOGLにはGearsという三つの歯車が回るデモがあります。AWTを使うのがGearsで,Swingを使用するJGearsです。JGearsには,1秒間に何回描画されるかを示すFPS(Frame per Second)の値が表示されます。実力を試すのにはおあつらえ向きですね。FPSが大きいほど,高速に実行されていることを示します。

使用したパソコンはPentium M 1.86GHz,NVIDIA Go 6200という構成のノート・パソコンです。OSはWindows XP SP2を使用しています。JavaはJ2SE 5.0u8とJava SE 6 beta 2を使用しました。

図3はJ2SE 5.0,図4はJava SE 6のデフォルト,図5はOpenGLを指定した場合のJGearsの実行結果です。

JavaはWindowsではデフォルトでDirectXを使用します。Java SE 6では,起動オプションに-Dsun.java2d.opengl=trueをつけることでOpenGLを指定できるようになっています。

図3~5を見ると,パフォーマンスの違いは明らかです。Java SE 6のデフォルトでも,J2SE 5.0に比較して3割程度パフォーマンスが向上しています。

さらに,OpenGLを使用した場合は,使用しないものに比べてパフォーマンスが5倍も向上しています。

もちろん,ウィンドウのサイズが大きくなると,パフォーマンスの差は小さくなります。しかし,それでも,この描画スピードであれば,Swingでも問題なくJOGLを使用できるのではないでしょうか。

J2SE 5.0での実行結果 Java SE 6での実行結果 Java SE 6(OpenGL使用)での実行結果
図3 J2SE 5.0での実行結果 図4 Java SE 6での実行結果 図5 Java SE 6(OpenGL使用)での実行結果

Swingで3Dのコンポーネントを作る

SwingでJOGLを使用する場合,GLCanvasクラスではなく,GLJPanelクラスを使用します。名前からわかるようにJPanelクラスの派生クラスになっています。

GLJPanelクラスを使う以外は,AWTもSwingもそれほど変わりはありません。違いはAWTとSwingの差に起因するものだけで,JOGLはAWTでもSwingでも全く同じように使用できます。

それでは,サンプルとして先週も登場したロボットに再登場してもらいましょう。

このロボットの歩くスピードを変更できるようにしたSwingのコンポーネントを作ってみたいと思います。サンプルはここからダウンロードできます: WalkingRobotPanel.java WalkingRobot.java WalkingRobotSample.java

SwingのコンポーネントになるのはWalkingRobotPanelクラスです。このクラスはGLJPanelクラスの派生クラスになります。

ただし,GLEventListenerインタフェースはインプリメントしていません。描画はWalkingRobotクラスに委譲することにしました。したがって,WalkingRobotクラスがGLEventListenerインタフェースをインプリメントしています。

public class WalkingRobotPanel extends GLJPanel {

歩くスピードを変更するために,JSliderクラスを使用しましょう。SwingのJComponentクラスはコンポーネントであると同時にコンテナにもなるので,WalkingRobotPanelクラスもコンテナとして使用できます。そこで,JSliderクラスも一緒に含んだコンポーネントにします。

せっかくなので,スピードも表示してみましょう。スピードの表示にはJLabelクラスなどを使ってもいいのですが,すでにJSliderクラスを使っているので,独自に描画させるようにしました。

WalkingRobotPanelクラスのコンストラクタは次のようになります。

  public WalkingRobotPanel() {
    robot = new WalkingRobot(this);

    // スピードを初期値にする
    // WalkingRobotオブジェクトには,スピードを
    // BASE(=100)で割ってから設定する
    speed = INIT_SPEED;
    robot.setSpeed(speed/BASE);
        
    setLayout(new BorderLayout());
        
    // スライダの生成,設定
    initSlider();
       
    // スピードを表示するためのテキストのアトリビュート
    attributes = new HashMap();
    attributes.put(TextAttribute.FONT, FONT);
    attributes.put(TextAttribute.FOREGROUND, Color.WHITE);
        
    animator = new Animator(this);
  }

WalkingRobotオブジェクトに対するスピードの設定にはsetSpeedメソッドを使用します。スライダで描画されるスピードは0から100にしたのですが,そのままだとWalkingRobotオブジェクトには大きすぎるので,100で割って,0から1の値を設定するようにしてあります。

attributesという変数は,スピードを表示するためのフォントやテキストの色といったプロパティを保持しています。

さて,スピードの表示です。3Dの描画処理に含めてもいいのですが,Java 2Dの方がテキスト処理に優れているので,Java 2Dを使用しました。

Swingではコンポーネントが独自に何かしらの描画を行うときは,paintComponentメソッドをオーバライドして描画処理を記述します。

  @Override
  public void paintComponent(Graphics g) {
    super.paintComponent(g);
 
    Graphics2D g2d = (Graphics2D)g;
        
    // テキスト処理はTextLayoutクラスを使用
    FontRenderContext renderContext
            = g2d.getFontRenderContext();
    TextLayout layout
            = new TextLayout("Speed: " + speed,
                             attributes, renderContext);
    // コンポーネントのサイズとテキストのサイズから
    // 描画位置を決める
    Rectangle2D dimension = g2d.getClip().getBounds2D();
    Rectangle2D bounds = layout.getBounds();
    float heiht = (float)(dimension.getHeight()
                  - bounds.getHeight());
 
    // スピードの描画
    layout.draw(g2d, 10.0f, height);
  }

メソッドの前にあるアノテーション@Overrideは親クラスのメソッドをオーバライドしたことを示すものです。

そして,メソッドの一番はじめに親クラスのpaintComponentメソッドをコールします。ここで3Dの描画が行われます。

次からがスピードを表示するためのテキスト処理です。テキスト処理にはTextLayoutクラスを使用します。Graphics#drawStringメソッドを使用してもいいのですが,詳細な設定をしたい場合にはTextLayoutクラスのほうが適しています。

3Dの描画処理は,すでにあるWalkingRobotクラスをほとんどそのまま使用しました。

起動クラスになるWalkingRobotSampleクラスは,フレームを生成してそこにWalkingRobotPanelオブジェクトを貼っているだけです。

  public WalkingRobotSample() {
    JFrame frame = new JFrame("Walking Robot");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(400, 300);
       
    frame.add(new WalkingRobotPanel());
    frame.setVisible(true);
  }

では実行してみましょう。ちゃんとスピードが変更できましたか?

Swingの利点の一つにコンポーネントの背景を透明にできる点があります。ここでも,JSliderの背景を透明にしてあるため,ロボットがスライダに重なっても正しく描画されていることがわかります。

このように,Java SE 6を使用すれば,SwingとJOGLを組み合わせたクールなUIを作れるのです。

さて,来週はJOGLの最終回です。そこで,JOGLと2Dが融合した実際のアプリケーションを紹介しましょう。

WlakingRobotSampleの実行結果
図6 WlakingRobotSampleの実行結果

著者紹介 櫻庭祐一

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