はじめに
前回の記事「3Dモデルを表示するJavaアプレットの作成 」では3Dモデルを表示するためのアプレットを紹介しました。前回は3D表示の基礎を学習することを目的としていたので、敢えてゼロから3D表示のためのプログラムを作りましたが、「Java3D」を使用すると3D表示を簡単に実現できます。今回はこのJava3Dを使用してシンプルなアプリケーションを1つ作成してみます。
しかし、Java3Dを使用して立体モデルの表示を行うサンプルはJava3Dをインストールすると付いてきますし、他のWebサイトでもたくさん紹介されていますので、Java3Dの導入と基本部分は他の情報元に譲ることとし、今回はせっかくですから今後の3Dアプリの開発に役立つツールを作ってみることにします。
Java3Dを実際に使ったことがある方は、3Dモデルの表示に使用する材質をどのように設定すべきか悩んだ経験があるかと思います。
材質を設定するにはMaterial
クラスを使用しますが、このクラスの属性に指定できる「拡散光」「環境光」「鏡面光」「放射光」の色にはそれぞれRGBの成分を指定する必要があり全部で4×3=12種類の値を決めなくてはなりません。さらに「輝度」と「透明度」も考慮すると、なんと14種類!
意図した表示結果を得るには、これらの値を適切に設定する必要があるのですが、なかなかイメージ通りの材質を得るのは容易ではありません。その結果、値を微妙に変更しながら実行結果を確認してやり直し、という試行錯誤の繰り返しになったりして、これはもう大変な作業です。
そこで今回は、Java3Dで使用する材質を図1のような画面で選択し、さらにはMaterial
クラスの属性を設定するために必要なJavaのコードを出力してくれるツールを作ってみることにします(ただし、光源は固定でテクスチャ画像も使用しないものとします)。
GUIにはSwingを使用するので、Java3DとSwingの両方の学習に役立てていただければ幸いです。
対象読者
Java3D、Swingの基礎を学びたい人、Java3D(またはOpenGLやDirectX)で材質の設定に苦労したことがある人が対象です。
必要な環境
J2SE Development Kit(JDK) 1.4.x以上、Java3D 1.3.x 以上が必要です。
Materialの属性
今回作成するアプリケーションはJava3Dで使用するMaterial
(材質)クラスの属性を定める各種パラメータを設定するためのものです。
ここで登場するAmbient(環境光)、Diffuse(拡散光)、Specular(鏡面光)、Emission(放射光)、Shiniess(輝度)は、それぞれMaterial
クラスのsetAmbientColor
、setDiffuseColor
、setSpecularColor
、setEmissiveColor
、setShininess
メソッドで設定できます。Transparency(透明度)は、TransparencyAttributes
クラスのsetTransparency
メソッドで設定します。
これらの用語はCGソフトを扱っていると目にすることがあると思いますが、それぞれ次のような意味を持ちます。
- Ambient(環境光):物体全体を均一に照らす光(直接光が届かない所をぼんやりと明るくする働きがあります)
- Diffuse(拡散光):光源からの光が物体に当たって散乱される光
- Specular(鏡面光):光源からの光が反射した光(「反射光」とも言います)
- Emission(放射光):物体そのものが発する光
- Shiniess(輝度):鏡面光の反射の鋭さ(値が大きいと小さな領域で鋭く反射し、値が小さいと広い領域でぼんやり反射します)
- Transparency(透明度):物体の透明度(値がゼロだとまったく透過せず、値が1になると完全に透明になります)
これらの全てを手動で設定するのは大変なので、一般によくある材質エディタでは、拡散光の色を決定すると他の色が自動的に決定されたり、放射光の項は最初から無いものとしている場合があります。
拡散光が赤なのに環境光が青で鏡面光が緑、という材質はあまり現実的でなく、このような妙な性質を持つ材質が必要になることは希なため、拡散光さえ決めてしまえしまえばそれを元に他の光を定めてしまっても問題無いことが多いです。
しかし、Java3Dでは(OpenGL、DirectXなども)それぞれの光の色を個別に設定できる機能が備わっているので、今回は全て独立して設定できるようにしてみました。
今回のアプリケーションを触って、いろいろ試してみるとそれぞれの色の意味するものがわかると思います。また、今まで見たことがないような面白い見え方をする材質を発見できるかもしれません。
Swingでアプリケーションの外観を作る
今回は、AWT(Abstract Window Toolkit)ではなくSwingを使用してアプリケーションのGUIを作成することにします。
Swingを用いたコーディングは基本的にはAWTを使用した場合と大きな違いはありませんが、Swingには色を選択するためのコンポーネントJColorChooser
クラスがあります。今回は様々な色を決定する必要があるため、このSwingに含まれるJColorChooser
を活用します。
今回作成するアプリケーションの基本となるクラスをMaterialEditor
とし、次のように宣言します。
public class MaterialEditor extends JFrame implements ActionListener, ChangeListener, AdjustmentListener { // (中略) }
上記の宣言の通り、メインのクラスはSwingのJFrame
を継承し、後から追加するSwingの部品からイベント通知を受け取るために、ActionListener
(ボタンのイベント用)、ChangeListener
(JColorChooser
のイベント用)、AdjustmentListener
(スクロールバーのイベント用)を実装します。
それぞれのイベントが発生した時に呼ばれるメソッドはactionPerformed
、stateChanged
、adjustmentValueChanged
です。
さて、まずはSwingの各UI部品を使ってアプリケーションの外観を決定します。今回のGUI部品のレイアウトの様子は図2のようになります。
図のようなレイアウトを実現するために、次のようにコードを記述します。
private void buildUI() { // 上部パネル // (レンダリングキャンパス+パラメータ編集パネル)の作成 JPanel panelNorth = new JPanel(); panelNorth.setLayout(new FlowLayout()); panelNorth.add(createCanvas()); panelNorth.add(createParamEditPanel()); // カラー選択パネルの作成 JPanel panelCenter = new JPanel(); colorChooser = new JColorChooser(); colorChooser.getSelectionModel().addChangeListener(this); panelCenter.add(colorChooser); // 下部パネル(コード出力ボタン)の作成 JPanel panelSouth = new JPanel(); panelSouth.add(outputButton = new JButton("コード出力")); outputButton.addActionListener(this); // 上部パネルと下部パネルをフレームに追加 getContentPane().setLayout(new BorderLayout()); getContentPane().add(panelNorth, BorderLayout.NORTH); getContentPane().add(panelCenter, BorderLayout.CENTER); getContentPane().add(panelSouth, BorderLayout.SOUTH); }
まず、ベースとなるJFrame
のコンテンツのレイアウトをBorderLayout
に設定し、NORTH("北"つまり上)、CENTER(中央)、SOUTH("南"つまり下)のそれぞれにJPanel
を配置します。
これによって、大きく分けて3つのパネルがアプリケーションに配置されます。
中央のパネルには、色選択用のJColorChooser
を1つ、下のパネルにはコード出力用のJButton
を1つ配置するだけですので、特に難しいことはありません。
上側のパネルはレイアウトをFlowLayout
にし、左側にレンダリング結果を表示する3Dキャンバス(createCanvas
メソッドで作成)を、右側にはパラメータを変更するためのボタンとスクロールバーを配置した別のパネル(createParamEditPanel
メソッドで作成)を配置しています。パラメータ編集用のパネルは、次のようにGridLayout
を用いて6行2列の部品の配置を行っています。
private JPanel createParamEditPanel() { JPanel panel = new JPanel(); panel.setPreferredSize(new Dimension(300, RENDER_CANVAS_SIZE)); panel.setLayout(new GridLayout(6, 2, 10, 5)); for(int i = 0; i < 6; i++) { // ラベルの追加 panel.add(paramNameLabels[i] = new JLabel(labelNames[i], JLabel.RIGHT)); if(i < 4) { // ボタンの追加 paramButtons[i] = new JButton(); panel.add(paramButtons[i]); paramButtons[i].addActionListener(this); } else { // スクロールバーの追加 int barIndex = i - 4; paramScrollBars[barIndex] = new JScrollBar(JScrollBar.HORIZONTAL, 0, 1, 0, 100); paramScrollBars[barIndex].addAdjustmentListener(this); panel.add(paramScrollBars[barIndex]); } } return panel; }
ここではBorderLayout
、FlowLayout
、GridLayout
の3つのレイアウトが登場します。
JavaのAPI仕様書を見ると、それぞれのレイアウト方法の特徴を理解できるでしょう。
アプリケーションのインターフェース
色の編集は画面中央のJColorChooser
を利用した色選択パネルで行います。
JColorChooser
は非常に便利なクラスで、色を選択するために次の3通りの方法を提供します。ユーザーはそれぞれから好きな方法で色を選択できます。
- サンプルカラーからの選択
- HSB値の指定
- RGB値の指定
ここで選択する色は、Ambient(環境光)、Diffuse(拡散光)、Specular(鏡面光)、Emission(放射光)の4つの光の色の1つに反映されます。対象となる光は、右上のラベルが青い四角の枠で囲まれています(起動時にはAmbient(環境光)が編集対象となっています)。
この編集対象を変更するには、変更したい色のボタン(ラベルの右隣の四角)を押します。
Shiniess(輝度)とTransparency(透明度)はスクロールバーで変更します。どちらも右に行くほど値が大きくなります。
Java3Dを用いてレンダリング結果を表示するキャンバス
3Dオブジェクトを表示するためのキャンバスがCanvas3D
クラスです。Java3Dでは、まずCanvas3D
オブジェクトを作成し、SimpleUniverse
クラスのコンストラクタに渡します。その後、SimpleUniverse
クラスのaddBranchGraph
メソッドを使用して3D要素を追加します。
Canvas3D
オブジェクトの作成は、次のように行っています。
private Canvas3D createCanvas() { GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration(); Canvas3D canvas = new Canvas3D(config); canvas.setSize(RENDER_CANVAS_SIZE, RENDER_CANVAS_SIZE); SimpleUniverse universe = new SimpleUniverse(canvas); universe.getViewingPlatform().setNominalViewingTransform(); universe.addBranchGraph(createBranchGraph()); return canvas; }
今回はcreateBranchGraph
メソッドで、球体を1つ表示するだけの簡単なシーンを作成します。このシーンを表すBranchGroup
には、次の要素を追加します。
- 平行光源(
Light
オブジェクト):右斜め上から球体を照らす白色光源 - 環境光(
Light
オブジェクト):球体全体を均一に照らす光源 - 背景イメージ(
Background
オブジェクト):球体の透明度を識別するために追加する黒と白のチェック柄のイメージ - 球体(
Sphere
オブジェクト):材質の設定結果を確認するための球体
次のコードようにBranchGroup
オブジェクトのaddChild
メソッドを使用して、これらの要素を登録します。
private BranchGroup createBranchGraph() { BranchGroup branchGroup = new BranchGroup(); // 平行光源の追加 branchGroup.addChild(createDirectionalLight()); // 環境光の追加 branchGroup.addChild(createAmbientLight()); //背景イメージの追加 branchGroup.addChild(createBackgroundImage()); // レンダリング対象の球体を追加 branchGroup.addChild(createSphere()); branchGroup.compile(); return branchGroup; }
Sphereオブジェクトの作成
Java3Dに準備されているSphere
クラスを使用することで、簡単に球体の3Dモデルを作成できます。
Sphere
クラスには6種類ものコンストラクタがありますが、今回は半径、法線の設定の指定、分割数(値が大きいほど滑らかな球体になります)、外観(Appearance
)の指定ができる次のコンストラクタを使用します。
Sphere(float radius, int primflags, int divisions, Appearance ap)
ここで指定するAppearance
が、Ambient(環境光)などに関係するMaterial
と、Transparency(透明度)に関係するTransparencyAttributes
を保持します。つまり、Sphereオブジェクトを作成する前に、各種設定を行ったAppearance
オブジェクトを準備しておく必要があります。
そのままではAppearance
やMaterial
の属性は後から変更できないようになっているので、setCapability
メソッドを使って、後から変更できるようにしておく必要があります。
文章では少しわかりにくいでしょうが、次のコードを見ると内容がわかると思います。
private Sphere createSphere() { // Material の作成 Material material = new Material(); material.setCapability(Material.ALLOW_COMPONENT_READ); material.setCapability(Material.ALLOW_COMPONENT_WRITE); material.setLightingEnable(true); // TransparencyAttributes の作成 TransparencyAttributes tansparency = new TransparencyAttributes(TransparencyAttributes.BLENDED, 0.0f); tansparency .setCapability(TransparencyAttributes.ALLOW_VALUE_READ ); tansparency .setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE ); // Appearance の作成 m_appearance = new Appearance(); m_appearance.setCapability(Appearance.ALLOW_MATERIAL_READ); m_appearance.setCapability(Appearance.ALLOW_MATERIAL_WRITE); m_appearance.setCapability( Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_READ); m_appearance.setCapability( Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_WRITE); // Appearance に Material を設定 m_appearance.setMaterial(material); // Appearance に TransparencyAttributes を設定 m_appearance.setTransparencyAttributes(tansparency); // Sphere の作成 Sphere sphere = new Sphere( 0.7f, Sphere.GENERATE_NORMALS, 128, m_appearance ); sphere.getShape().setCapability(Shape3D.ALLOW_APPEARANCE_WRITE); sphere.getShape().setCapability(Shape3D.ALLOW_APPEARANCE_READ); return sphere; }
材質設定用のコードを出力する
各パラメータを変更してレンダリング結果の変化を見るだけでも楽しいですが、せっかくなのでここで編集した材質をJava3Dを用いたプログラムで使用できるように、Javaのコードを出力する機能を追加してみます。
今回は、アプリケーションの下の方にある「コード出力」ボタンを押すと次のようなフレームが現れ、テキストエリアにコードが表示されるようにします。
コードをマウスドラッグで選択して、[Ctrl]+Cボタンでクリップボードにコピーすれば、任意のテキストエディタにコピーすることができます。
コードの出力は、テキストエリアに値を書き出すだけですので大したことは行っていませんが、DecimalFormat
クラスを使用して小数点を含む値の出力形式を整えています。
次のようにDecimalFormat
クラスのコンストラクタに"0.0000"
という文字列を指定すると、小数点以下4桁までを数字で出力するように設定できます。
DecimalFormat form = new DecimalFormat("0.0000");
このコンストラクタで作成したオブジェクトのformat
メソッドを使用すると、指定したフォーマットで値を文字列に変換できます。
今回はJava言語で材質を設定するためのコードを出力するようにしましたが、必要に応じてC言語などでOpenGLやDirectXを使用するためのコードに変更してもよいでしょう。
private void outputCode() { Material material = m_appearance.getMaterial(); Color3f color = new Color3f(); JFrame frame = new JFrame("Code"); frame.setBounds( 100, 100, 500, 250); JTextArea textArea = new JTextArea(); textArea.setLineWrap(true); DecimalFormat form = new DecimalFormat("0.0000"); textArea.append("Material material = new Material();\n"); material.getAmbientColor(color); textArea.append("material.setAmbientColor(" + form.format(color.x) + "f, " + form.format(color.y) + "f, " + form.format(color.z) + "f);\n"); // (中略) frame.add(textArea); frame.setVisible(true); }