Java3D自分FAQ


これは、私がJava3Dを始めるに当たってぶち当たった壁を列挙し、 可能ならばそれを解決した方法をあげたページです。 よってあんまり正しいFAQの形式をとってません。 よってこの解法が正しいかどうか保証はありません。 よって鵜呑みにするとひどいめにあうかもしれません。

Q1. Java3Dを始めるには何が必要でしょうか?

本家JavaSoftページ Java 3D(TM) API Home Page を参照しましょう。ここから必要最低限の情報は得られます。

とりあえず、Win環境でJava3Dプログラムを始めるには、以下のものが必要です。

Q2. サンプルHelloUniverseをVisual Cafeでコンパイルしようと思うのですが

注)以下の内容は古いJDKおよびJava3Dをもとにした内容なので、現在のJava3Dとは一致しません。 また、今はVisual Cafeを利用していないため、現在のバージョンでどのような設定が 必要かは調べていません。それほど変化はないとは思います。

プロジェクトの設定で、クラスパスを設定できるのですが、Java3Dの各種jarファイル を設定するのがうまくいきませんでした。そこで、Visual CafeのBin配下にある、 sc.iniを以下のようにしてしまいました。

PATH=%@P%\..\BIN;%@P%\..\Java\bin;%PATH%;c:\java3d\bin;c:\jdk1.2beta3\bin;
BIN=c:\jdk1.2beta3\bin
INCLUDE=%@P%\..\INCLUDE
LIB=%@P%\..\Lib
HELP=%@P%\..\HELP
JAVAINC=%@P%\..\JAVA\SRC
CLASSPATH=.;%@P%\COMPONENTS\SYMBEANS.JAR;%@P%\..\JAVA\LIB;%@P%\..\JAVA\LIB\SYMCLASS.ZIP;
%@P%\..\JAVA\LIB\CLASSES.ZIP;%@P%\..\JAVA\LIB\DBAW.ZIP;%@P%\COMPONENTS\DBAW_AWT.JAR;
c:\java3d\lib\appext\j3dutils.jar;c:\java3d\lib\sysext\j3dcore.jar;
c:\java3d\lib\sysext\j3daudio.jar;c:\jdk1.2beta3\lib\classes.zip
JAVA_HOME=c:\jdk1.2beta3
要はここのクラスパス設定に、Java3D関係およびJDK1.2のクラスファイルを 設定してしまいました。あと、java3dのbinにもパスを通しておきました。 jdk1.2beta3をBINやJAVA_HOMEに設定することに意味があるかどうかは 分かりません。

Q3. Hello Universeは実行できましたが、たまに黒い画面しか出て こなかったりするのですが

Java3D 1.1 Alpha 1だとよく発生しましたが、Alpha 2にしてからは 発生しませんので、よしとしましょう。

Q4. HelloUniverseにでてくるTransformGroup,BranchGroupって何? シーングラフって?

HelloUniverseを見ていると、TransformGroupやBranchGroupなどが 立方体の制御をしているように見えます。これらはシーングラフを 構成するノードの1種です。

まず、シーングラフってどんな感じのものってことを、 Java 3D API Specificationの、Scene Graph Basics を読んで漠然ととらえましょう。

基本的には、Figure 2-1のような木構造でJava3Dのシーンを呼ばれるものが 構成されています。この木構造のグラフをシーングラフと呼び、 このシーングラフによって、Java3Dで描画する仮想世界を表現します。 その木のノード(木の構成要素)として以下の3つをとりあえず使ってみましょう。 BranchGroupの下に、TransformGroupとShape3Dをぶら下げた木で、 一つの物体を表現します。

Q5. HelloUniverseにでてくるSimpleUniverseってどう使うの?

HelloUniverseでも使われている、 SimpleUniverse(com.sum.j3d.utils.universe.SimpleUniverse) を用いることで、シーングラフおよびそれを表示するCanvas3Dを設定できます。 Canvas3Dは、シーングラフで表現された仮想世界を表示する、 仮想世界の覗き穴のようなものです。 設定は、以下のようにします。

    BranchGroup scene = new BranchGroup();
    Canvas3D canvas3D = new Canvas3D();
    SimpleUniverse u = new SimpleUniverse(canvas3D);
    u.addBranchGraph(scene);
このようにすることで、Canvas3D上に、BranchGroup sceneを根にした シーングラフが表示されます。
このシーングラフにQ4で述べた形の「物体」を追加します。 この物体を表すクラスを定義すると便利そうです。 ここではObjTransというクラスを定義しています。雛形は以下の通りです。

import javax.media.j3d.*;
import javax.vecmath.*;

public class ObjTrans {
  public TransformGroup objTrans;

  private Group root;
  private BranchGroup bg;
  
  public ObjTrans(Shape3D shape, Group group) {
    root = group;
    objTrans = new TransformGroup();
    objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    bindShape(shape);
  }
    
  public void bindShape(Shape3D shape) {
    objTrans.addChild(shape);
    bg = new BranchGroup();
    bg.setCapability(BranchGroup.ALLOW_DETACH);
    bg.addChild(objTrans);
    setPosition();
    root.addChild(bg);
  }
}
ObjTransをシーングラフに加えるには以下のようにします。

    BranchGroup scene = new BranchGroup();
    Canvas3D canvas3D = new Canvas3D();
    SimpleUniverse u = new SimpleUniverse(canvas3D);
    u.addBranchGraph(scene);
    
    ObjTrans ot = new ObjTrans(new ColorCube().getShape(), scene);
ObjTransコンストラクタの第1引数にShape3Dを、 第2引数にそのObjTransを加えるシーングラフ中のノードを指定します。 この場合は、先ほど用意した、シーングラフの根っこ、sceneを指定しました。

Q6. TransformGroupを使って物体の位置決めをしたいのですが

空間(x,y,z)に角度(d1,d2,d3)で存在する物体をTransformGroupで指定する には、以下のようにしてみました。ObjTransに以下のメソッドを加えます。

  public void setPosition(float x, float y, float z, float d1, float d2, float d3) {
    Transform3D trns = new Transform3D();
    Transform3D trnsX = new Transform3D();
    Transform3D trnsY = new Transform3D();
    Transform3D trnsZ = new Transform3D();
    trnsY.rotY(d1);
    trnsX.rotX(d2);
    trnsZ.rotZ(d3);
    trns.mul(trnsZ, trns);
    trns.mul(trnsX, trns);
    trns.mul(trnsY, trns);
    trns.setTranslation(new Vector3f(x, y, z));
    objTrans.setTransform(trns);
  }
注) これで一応動きますが、このメソッドは、かなり高価(時間がかかる)です。 可能な限り、Transform3Dに用意されているメソッドを用いて、最小限の計算ですむように した方がいいです。また、このようなメソッドを用いる場合でも、使用するTransform3Dは あらかじめnewで生成しておくべきです。オブジェクトの生成は、かなりのオーバヘッドになります。

回転用の4x4行列を含んだtrnsX,Y,Zを用意し、順にtrnsに乗じて いきます。最後にVector3fで指定された並行移動を適用し、 できあがったTransform3Dオブジェクトを、TransformGroupにセットします。 とりあえず、これで正しく動作しますが、無駄があるかもしれません。

あと、このTransformGroup(上のObjTrans)に書き込みを許可することを 忘れないようにしましょう。書き込みを許可せずに、すでにレンダリングが 行われている物体のTransformGroupに設定を行おうとするとExceptionが 発生します。書き込みを許可するには、

    objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
とします。ObjTransではコンストラクタ中で行っています。

Q7. 複数の物体を扱いたいのですが

Q5の要領で、sceneに次々にObjTransを加えていけば複数の物体を 扱えます。

Q8. 物体を追加しようとするとExceptionが発生するのですが

レンダリングが始まってからのノードの追加は、Q6と同様、 明示的に許可しなければなりません。sceneに対して ノードの追加を許可するには、

    scene.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
とします。

Q9. 物体をシーンから消すには?

ObjTransの根っこのBranchGroupをdetach()します。するとこのObjTransは シーングラフから外され、シーンから消えます。

  public void unbind() {
    bg.detach();
  }
BranchGroupをdetachするにはdetachが許可する必要があります。

    bg.setCapability(BranchGroup.ALLOW_DETACH);
それに加えて、detachするノードの上位ノードで、子ノードのWRITEを許可する 必要もあります。

    scene.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);

Q10. 物体の形をつくるには?

Shape3Dクラスで物体の形を定義します。Shape3Dのコンストラクタは、

  new Shape3D(Geometry geometry)
ですから、Geometryクラスで形を定義できそうです。

GeometryクラスのサブクラスGeometryArrayを使って点や線や三角形、四角形から 構成される物体の形を生成できます。 四角形で構成される物体の形を定義するには、以下のようにします。

    float[] verts = {
      8f,0,8f, 8f,0,-8f, -8f,0,-8f, -8f,0,8f,
      8f,-0.8f,8f, -8f,-0.8f,8f, -8f,-0.8f,-8f, 8f,-0.8f,-8f, 
    };
    float[] colors = {
      0.2f, 1f, 0.5f, 0.2f, 1f, 0.5f, 0.2f, 1f, 0.5f, 0.2f, 1f, 0.5f, 
      0.5f, 1f, 0.2f, 0.5f, 1f, 0.2f, 0.5f, 1f, 0.2f, 0.5f, 1f, 0.2f, 
    };
    QuadArray geo = new QuadArray(8,
        QuadArray.COORDINATES | QuadArray.COLOR_3);
    geo.setCoordinates(0, verts);
    geo.setColors(0, colors);
GeometryArrayのサブクラスQuadArrayを使います。メソッドsetCoordinatesおよび setColorsで各面の頂点の場所と色を指定します。4つの頂点が一つの四角形の面を 表します。同じ面の頂点の色を別々にすると、グラデーションがかった面になります。

四角形、三角形以外の面を定義したり、それらを混ぜて物体を構成する方法は まだよく分かってません。だれか教えてください。

Q11. 視点を変えるには?

SimpleUniverseから視点の位置に相当するTransformGroupを取り出すことができます。

    ViewingPlatform vplt = u.getViewingPlatform();
    viewpoint = vplt.getViewPlatformTransform();
あとはこのTransformGroupをQ6のように変化させれば視点が変化します。

Q12. ライトの設定をするには?

シーングラフにLightを加えるだけです。例えば、directional lightを 加えたい場合は、

    BranchGroup bgLt = new BranchGroup();
    BoundingSphere bounds = new BoundingSphere(new Point3d(0, 0, 0), 100.0);
    bgLt.addChild(new BoundingLeaf(bounds));
    DirectionalLight drcl =
        new DirectionalLight(Boot.gray, new Vector3f(0, -1, 0));
    drcl.setInfluencingBounds(bounds);
    bgLt.addChild(drcl);
とでもして、クラスDirectionalLightのインスタンスを持つBranchGroupを作り、

    scene.addChild(bgLt);
とします。BoundingLeafというのは、その光の有効範囲を決めているのだと 思いますが、詳しいことはわかってません。丸写しです。

あと、PointLightとかにもいえるのですが、光の位置や方向がこちらの意図する 通りに反映されていないような気がします。なにか設定に間違いがあるかも しれません。

Q13. ライトを設定してもなんにもかわらないのですが

物体にライトを反映するには、物体の形を定義する際に、面の法線を設定する 必要が有ります。Q10のように形を定義する際に、

    float[] verts = {
      8f,0,8f, 8f,0,-8f, -8f,0,-8f, -8f,0,8f,
      8f,-0.8f,8f, -8f,-0.8f,8f, -8f,-0.8f,-8f, 8f,-0.8f,-8f, 
    };
    float[] colors = {
      0.2f, 1f, 0.5f, 0.2f, 1f, 0.5f, 0.2f, 1f, 0.5f, 0.2f, 1f, 0.5f, 
      0.5f, 1f, 0.2f, 0.5f, 1f, 0.2f, 0.5f, 1f, 0.2f, 0.5f, 1f, 0.2f, 
    };
    float[] normals = {
      0,1,0, 0,1,0, 0,1,0, 0,1,0, 
      0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0,
    };
    QuadArray geo = new QuadArray(8,
        QuadArray.COORDINATES | QuadArray.COLOR_3 | QuadArray.NORMALS );
    geo.setCoordinates(0, verts);
    geo.setColors(0, colors);
    geo.setNormals(0, normals);
として、法線をメソッドsetNormalsを使って設定します。これに加えて、

    Appearance ap = new Appearance();
    Material mt = new Material();
    mt.setLightingEnable(true);
    ap.setMaterial(mt);
として、物体のAppearanceを設定し、光が反映するように設定します。 そして、

    new Shape3D(geo, ap);
とすれば、ライトが反映される物体になります。

Q14. ポリゴンではなくワイヤーフレームで表示するには?

Q13のAppearanceで設定します。

    Appearance ap = new Appearance();
    PolygonAttributes pa = new PolygonAttributes();
    pa.setPolygonMode(PolygonAttributes.POLYGON_LINE);
    ap.setPolygonAttributes(pa);
のように、PolygonAttributesを設定し、Appearanceにセットします。

Q15. Canvas3D上に文字を書きたいのですが

Canvas3DのメソッドpostRenderで、ダブルバッファの裏面を取得し、 そのバッファに書き込みを行えばいいのだと思うのですが、 取得する方法が分かりません。だれか教えてください。

一応postSwap中で、getGraphicsでCanvasのGraphicsを取得し、 それに文字を書き込めば画面には反映されますが、当然ちらついてしまいます。

Q16. 文字を空間に表示するには?

com.sun.j3d.utils.geometry.Text2Dを使えば、文字がテクスチャに張られた 板を得ることができます。Text2Dのコンストラクタを使って、

  Text2D textObject = new Text2D("Hello!",
    new Color3f(1f, 1f, 1f),
    "Serif",
    24,
    Font.BOLD);
などと設定するだけで、textObjectにShape3D(Text2DはShape3Dのサブクラスです) を得ることができます。ただ、このままだと文字があまりに小さいので、 Transform3DのsetScaleメソッドなどを使って、適当に拡大した方がよいでしょう。 コンストラクタの第4引数のフォントサイズでも大きさを調節できますが、 あまり大きくするとすぐにメモリが足りなくなります。

あと、文字を表示するたびにText2Dを生成すると、結構な時間がかかってしまいます。 そこで、あらかじめText2Dのインスタンスを作っておいて、必要に応じて cloneNodeメソッドによって複製するとよいでしょう、と、思ったのですが、 複製するにはText2DのAppearanceが読み書きできなければいけないらしく、 それらの属性をいちいち許可しないと複製できませんでした。

    Appearance app = textObject.getAppearance();
    app.setCapability(Appearance.ALLOW_COLORING_ATTRIBUTES_READ);
    app.setCapability(Appearance.ALLOW_LINE_ATTRIBUTES_READ);
    app.setCapability(Appearance.ALLOW_MATERIAL_READ);
    app.setCapability(Appearance.ALLOW_POINT_ATTRIBUTES_READ);
    app.setCapability(Appearance.ALLOW_POLYGON_ATTRIBUTES_READ);
    app.setCapability(Appearance.ALLOW_RENDERING_ATTRIBUTES_READ);
    app.setCapability(Appearance.ALLOW_TEXGEN_READ);
    app.setCapability(Appearance.ALLOW_TEXTURE_ATTRIBUTES_READ);
    app.setCapability(Appearance.ALLOW_TEXTURE_READ);
    app.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_READ);
    app.setCapability(Appearance.ALLOW_COLORING_ATTRIBUTES_WRITE);
    app.setCapability(Appearance.ALLOW_LINE_ATTRIBUTES_WRITE);
    app.setCapability(Appearance.ALLOW_MATERIAL_WRITE);
    app.setCapability(Appearance.ALLOW_POINT_ATTRIBUTES_WRITE);
    app.setCapability(Appearance.ALLOW_POLYGON_ATTRIBUTES_WRITE);
    app.setCapability(Appearance.ALLOW_RENDERING_ATTRIBUTES_WRITE);
    app.setCapability(Appearance.ALLOW_TEXGEN_WRITE);
    app.setCapability(Appearance.ALLOW_TEXTURE_ATTRIBUTES_WRITE);
    app.setCapability(Appearance.ALLOW_TEXTURE_WRITE);
    app.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_WRITE);
これ本当にこう書くしかないんでしょうか。面倒なのですが。
(これは後で、もう少しスマートな方法がありました。Q18を参照してください)

Q17. 3Dの文字は?

当然あります。Font3DクラスとText3Dクラスを使います。

    float sl = str.length();
    Font3D f3d = new Font3D(new Font("dialog", Font.PLAIN, 2),
        new FontExtrusion());
    Text3D txt = new Text3D(f3d, str,
        new Point3f( -sl/2.0f, -1.f, -1.f));
    Shape3D sh = new Shape3D();
    sh.setGeometry(txt);
こんな感じです。できあがった文字を表示すると、結構重いです。

Q18. 同じ形の物体を複数表示するときに、良い方法は?

SharedGroupと、Linkを使いましょう。Shape3DをBranchGroupでなくて、 SharedGroupにbindしておけば、あとでLinkを用いて、シーングラフ中の 複数の場所からそのShape3Dを用いることができます。

    SharedGroup sg = new SharedGroup();
を参照するには、

    new Link(sg);
とします。
(これを使えば、Q16のようにいちいち形状をcloneする必要はありません)

Q19. JBuilder2でJava3Dを扱うには?

JBuilder2はJava3Dを扱う際に便利な機能があります。

まず、ファイル→プロジェクトプロパティのパスタグの中のJavaライブラリに、 Java3Dを加えましょう。 追加を選んで、クラスパス、ソースパスおよび文章パスを加えます。 デフォルトのパスにJava3Dがインストールしてあるならば、 という具合でしょう。

また、ターゲットJDKバージョンをJDK1.2にする必要もあります。定義を押して、設定しましょう。

この設定をすれば、Java3DをJBuilder2から利用できます。 また、Java3Dのクラス名などの上でF1キーを 押すことで、対応するJavaDocをHelpとしてみることが、 β版まではできたのですが、リリース版はJavaDocがJDK1.2の形式になってしまったため、 JBuilder2上から参照することができません。β版のJava3DのDOCを持ってくるか、 Java3Dのクラスから、JDK1.1のJavaDocでHTMLファイルを生成すれば参照可能です。 (これ結構致命的なんですけど。Inpriseさん直してくれませんかね)

Q20. 半透明を出すには?

TransparencyAttributesを用います。

    Appearance ap = new Appearance();
    TransparencyAttributes ta = new TransparencyAttributes();
    ta.setTransparency(0.1f);
    ap.setTransparencyAttributes(ta);
として物体の透明度を設定した後に、Appearanceに設定します。

Q21. HUDのような、視点前に固定された物体を表示するには?

以下のようにして、PlatformGeometryを取得して、その下に 物体を追加します。

    Locale locale = new Locale(this);
    ViewingPlatform vplt = new ViewingPlatform(1);
    vplt.setNominalViewingTransform();
    viewpoint = vplt.getViewPlatformTransform();
    PlatformGeometry vPg = new PlatformGeometry();
    vplt.setPlatformGeometry(vPg);
    vPg.setCapability(Group.ALLOW_CHILDREN_EXTEND);
    vPg.setCapability(Group.ALLOW_CHILDREN_WRITE);
SimpleUniverseが提供するgetViewingPlatformで、ViewingPlatformを 取得した場合、Capabilityを変更することができませんので、 SimpleUniverseを使わないようにしましょう。

Q22. イミーディエートモードって? どうやって使うの?

通常のモード(リテインドモード)の場合、Java3Dのレンダリングエンジンは、 自動的にシーングラフを画面上に描画してくれます。描画のタイミングなどを ユーザが管理する必要はありません。これは、逆にいえば、ユーザが管理することが できないということでもあります。

リテインドモードでは、Java3Dのレンダリングエンジンが、シーングラフ上のCapabilityなどを 判断条件に、最適化されたレンダリングを行っています。しかし、いくつかのアプリケーションでは、 シーングラフを用いるほどの高度な3D空間を表現する必要がないかもしれません。 たとえば、いくつかのShape3Dを画面上に表示したいが、それらを奥行きでソートする必要がない場合などは、 シーングラフの助けを得るまでもありません。

このような場合、イミーディエートモードを用いることで、より高速化が図れる可能性があります。 ただし、イミーディエートモードでは、画面描画のタイミングや、物体の表示順番などは すべてユーザが面倒をみなければなりません。

イミーディエートモードを使うには、まずJava3Dのレンダリングエンジンを止め、 物体を直接描画するためのGraphicsContext3Dを取得する必要があります。

    canvas3D.stopRenderer();
    GraphicsContex3D gc = canvas3D.getGraphicsContext3D();
GraphicsContext3DのsetModelTransform(Transform3D trns)メソッドを用いて、物体の位置を決め、 draw(Shape3D shape)メソッドを用いてShape3Dを描画することができます。

    gc.setModelTransform(trns);
    gc.draw(shape);
また、drawメソッドでは、通常Canvas3Dの裏画面に描画されます。そこで、すべての物体が書き終わった 時点で、画面をスワップする必要があります。

    canvas3D.swap();

もどる