javax.media.j3d.Light
)今までは頂点に色を適用して描画してきました。Java 3Dには javax.media.j3d.Light
クラスがあり、光源を定義することができます。
クラス継承 java.lang.Object | +--javax.media.j3d.SceneGraphObject | +--javax.media.j3d.Node | +--javax.media.j3d.Leaf | +--javax.media.j3d.Light | |--javax.media.j3d.AmbientLight // 環境光 | |--javax.media.j3d.DirectionalLight // 平行光源 | +--javax.media.j3d.PointLight // 点光源 | +--javax.media.j3d.SpotLight // スポットライト クラス宣言 public abstract class Light extends Leaf
javax.media.j3d.Light
クラスは抽象クラス (abstcact クラス) であり、実際にはそのサブクラスの AmbientLight, DicectionalLight, PointLight, SpotLight
クラスが使用されます。
Light
は次の手順で使用します。
Light
オブジェクトを生成するLight
オブジェクトの setInfluencingBounds()
メソッドで、光源が作用する範囲を設定するGroup
ノードに addChild()
する実は上記以外に、照明される物体に「法線ベクトル」というものが設定されていないと、照明は有効になりません。
ここでは、照明による効果を確認するため、あらかじめ法線ベクトルが設定済みの com.sun.j3d.utils.geometry.Sphere
を使います。
自分で物体の形状を定義してある場合、法線ベクトルも自分で設定しなければなりません。法線ベクトルの計算方法、設定方法に付いては後で詳しく説明します。
javax.media.j3d.DirectionlLight
)平行光源は、太陽光のように(ほぼ)無限遠に光源があるもの、と考えることができます。
javax.media.j3d.DirectionalLight
は平行光源をシミュレートします。
クラス宣言 public class DirectionalLight extends Light コンストラクター public DirectionalLight() public DirectionalLight(Color3f color, // 光源の色 Vector3f direction) // 光の方向 public DirectionalLight(boolean lightOn, // 点灯しているかどうか Color3f color, // 光源の色 Vector3f direction) // 光の方向
引数なしのコンストラクターでは、lightOn == true
(点灯している状態)、光源の色は白 (1.0f, 1.0f, 1.0f)
、光の方向は Z軸のマイナス方向 (0.0f, 0.0f, -1.0f)
、つまり視点から画面奥に向かう方向になります。(自動車などの「ヘッドライト」を想像してください)
光源に色を付けると、照明された物体はその色で照らされます。
光の方向は、javax.vecmath.Vector3f
で設定します。Transform3D
の set()
メソッドで Vector3d
を使ったときは、移動先の座標を定義するために使用しましたが、Light
で使用する Vector3f
は方向ベクトルとして使用します。
クラス宣言 public class Vector3f extends Tuple3f implements java.io.Serializable コンストラクター (一部) public Vector3f(float x, // ベクトルの x 成分 float y, // ベクトルの y 成分 float z) // ベクトルの z 成分 正規化のためのメソッド (一部) public final void normalize()
光の方向に限らず、方向ベクトルは正規化 (ベクトルの長さを 1.0 にする) すると効率良く処理できます。Vector3f
は、正規化のためのメソッド normalize()
を持っています。
DicectionalLight
での光の方向は、TransformGroup
の影響を受けることに注意してください。TransformGroup
に addChild()
された DirectionalLight
は、その TransformGroup
に回転が適用されれば一緒に回転します。
LightingApplet.java1 // Java 3D Test Applet 2 // LightingApplet.java 3 // Copyright (c) 1999 ENDO Yasuyuki 4 // mailto:yasuyuki@javaopen.org 5 // http://www.javaopen.org/j3dbook/index.html 6 7 import java.applet.*; 8 import java.awt.*; 9 import javax.media.j3d.*; 10 import javax.vecmath.*; 11 import com.sun.j3d.utils.applet.MainFrame; 12 import com.sun.j3d.utils.universe.SimpleUniverse; 13 import com.sun.j3d.utils.geometry.Sphere; 14 15 public class LightingApplet extends Applet { 16 public LightingApplet() { 17 GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration(); 18 Canvas3D canvas = new Canvas3D(config); 19 this.setLayout(new BorderLayout()); 20 this.add(canvas, BorderLayout.CENTER); 21 22 SimpleUniverse universe = new SimpleUniverse(canvas); 23 universe.getViewingPlatform().setNominalViewingTransform(); 24 25 universe.addBranchGraph(createSceneGraph()); 26 } 27 28 private BranchGroup createSceneGraph() { 29 BranchGroup root = new BranchGroup(); 30 31 DirectionalLight light = new DirectionalLight();....................(1) 32 BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0);...(2) 33 light.setInfluencingBounds(bounds);.................................(3) 34 root.addChild(light);...............................................(4) 35 36 Sphere sphere = new Sphere(0.6f);...................................(5) 37 root.addChild(sphere); 38 39 return root; 40 }
(1)で DicectionalLight
を生成しています。引数無しのデフォルトのコンストラクターを使ったので、光源の色は白、方向はマイナス Z方向です。
(2)で BoundingSphere
を生成し、(3)で setInfluencingBouns()
メソッドを使って光源が作用する範囲として設定しています。
(4)で BranchGroup
に addChild()
しています。
(5)でcom.sun.j3d.utils.geometry.Sphere
オブジェクトを半径 0.6 で生成しています。
Sphere
は ColorCube
などと同様、あらかじめ形状が定義されたテスト用のオブジェクトです。
物体が照明を反映して見えるためには、法線ベクトルというもの(後述)を設定する必要がありますが、Sphere
にはデフォルトの状態で法線ベクトルが設定されています。このため、addChild()
するだけで表示されます。
このプログラムの実行結果は次のようになります。
光源に色を設定してみます。
LightingApplet.java (一部)28 private BranchGroup createSceneGraph() { 29 BranchGroup root = new BranchGroup(); 30 31 DirectionalLight light = 32 new DirectionalLight( new Color3f(1.0f, 0.0f, 0.0f), // 赤 33 new Vector3f(0.0f, 0.0f, -1.0f) ); // マイナスZ方向...(1) 34 BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0); 35 light.setInfluencingBounds(bounds); 36 root.addChild(light); 37 38 Sphere sphere = new Sphere(0.6f); 39 root.addChild(sphere); 40 41 return root; 42 }
(1)で DirectionalLight
を生成しています。引数の Color3f
に赤色を指定しています。
これ以外の部分は前のサンプルと変わりません。
このプログラムの実行結果は次のようになります。
このサンプルでは球体しかありませんが、ほかの物体があっても赤く照明されます。
色と方向が異なる、2つの光源を addChild()
するとどうなるでしょうか。
28 private BranchGroup createSceneGraph() { 29 BranchGroup root = new BranchGroup(); 30 31 BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0); 32 33 DirectionalLight redLight = 34 new DirectionalLight( new Color3f(1.0f, 0.0f, 0.0f), // 赤 35 new Vector3f(0.87f, 0.0f, -0.5f) ); // 斜め左方向から照明 ...(1) 36 redLight.setInfluencingBounds(bounds); 37 root.addChild(redLight); 38 39 DirectionalLight greenLight = 40 new DirectionalLight( new Color3f(0.0f, 1.0f, 0.0f), // 緑 41 new Vector3f(-0.87f, 0.0f, -0.5f) ); // 斜め右方向から照明 ...(2) 42 greenLight.setInfluencingBounds(bounds); 43 root.addChild(greenLight); 44 45 Sphere sphere = new Sphere(0.6f); 46 root.addChild(sphere); 47 48 return root; 49 }
(1)で赤い光源、(2)で緑色の光源を生成し、光源が作用する範囲を設定し、addChild()
しています。
照明の方向はそれぞれ斜め左方向からと、斜め右方向から照明されるように Vector3f
を設定しています。
このプログラムの実行結果は次のようになります。
平行光源以外の光源には、点光源、環境光、スポットライトがあります。
javax.media.j3d.PointLight
)javax.media.j3d.PointLight
は(電球のような)点光源をシミュレートします。
クラス宣言 public class PointLight extends Light コンストラクター public PointLight() public PointLight(Color3f color, // 光源の色 Point3f position, // 光源の位置座標 Point3f attenuation) // 光の減衰係数 public PointLight(boolean lightOn, // 点灯しているかどうか Color3f color, // 光源の色 Point3f position, // 光源の位置座標 Point3f attenuation) // 光の減衰係数
光源の色に関しては DirectionalLight
と同じです。
光源の位置については、Point3f
で指定した位置から照明されます。ここで面白いのは、光源から照明された光(による照明効果)は描画されますが、光源そのものは描画されない、ということです。光源がどこにあるかを見せるには、光源位置に何か物体を描画するしかありません。
光の減衰係数は、ちょっと複雑です。
Point3f
を引数にとりますが、これは座標として使用されるものではありません。
PointLight
は減衰係数を設定する setAttenuation()
メソッドも持っていますので、このメソッドで説明します。
public final void setAttenuation(float constant, // 減衰定数 float linear, // 減衰一次係数 float quadratic) // 減衰二次係数 public final void setAttenuation(Point3f attenuation)
減衰係数は、距離に応じた光の減衰を計算するために使用されます。この減衰式は、Java 3D API Specificatoin: Appendix E.Equations E.2 Light Equeations に、式 (E.7) として掲載されています。
atteni = 1/(Kci + Kli・di + Kqi・di2)
di | 距離 |
Kci | 減衰定数 |
Kli | 減衰一次係数 |
Kqi | 減衰二次係数 |
PointLight
で使用する Point3f attenuation
の x, y, z
は、それぞれ次のような意味を持っています。
x | Kci | 減衰定数 |
y | Kli | 減衰一次係数 |
z | Kqi | 減衰二次係数 |
減衰係数についてまとめると次のようになります。
PointLight
では、光の減衰を次の3つの係数で計算する
PointLight
の光の減衰は、定数減衰、距離に反比例する減衰、距離の2乗に反比例する減衰の和であるPointLight
のコンストラクターでは、減衰係数を Point3f
で設定する。各フィールドはそれぞれ次の定数、係数に対応している
x =
減衰定数y =
減衰一次係数z =
減衰二次係数PointLight
の引数無しのコンストラクターでは、lightOn == true
(点灯している状態)、光源の色は白 (1.0f, 1.0f, 1.0f)
、光源の位置は原点 (0.0f, 0.0f, 0.0f)
、減衰係数は (1.0f, 0.0f, 0.0f)
になります。
LightingApplet.java (一部)28 private BranchGroup createSceneGraph() { 29 BranchGroup root = new BranchGroup(); 30 31 PointLight light = 32 new PointLight( new Color3f(1.0f, 1.0f, 1.0f), // 光源の色 33 new Point3f(0.5f, 0.5f, 0.8f), // 光源の位置 34 new Point3f(0.7f, 0.0f, 0.0f) ); // 光の減衰定数、係数 ...(1) 35 BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0); 36 light.setInfluencingBounds(bounds); 37 root.addChild(light); 38 39 Sphere sphere = new Sphere(0.6f); 40 root.addChild(sphere); 41 42 return root; 43 }
(1)で PointLight
を生成しています。光源の色は白、光源の位置は、後で追加する球体の右上あたりの位置、減衰係数は定数値だけ設定しています。
DirectionalLight
のときと同様に、setInfluencingBounds()
メソッドで作用する範囲を設定し、BranchGroup
に addChild()
しています。
このプログラムの実行結果は次のようになります。
javax.media.j3d.AmbientLight
)javax.media.j3d.AmbientLight
は、DirectionalLight, PointLight
のような直接光ではなく、周囲に散乱した間接光 (環境光) をシミュレートします。
クラス宣言 public class AmbientLight extends Light コンストラクター public AmbientLight() public AmbientLight(Color3f color) // 光源の色 public AmbientLight(boolean lightOn, // 点灯しているかどうか Color3f color) // 光源の色
引数無しのコンストラクターでは、lightOn == true
(点灯している状態)、デフォルトの色は白です。
AmbientLight
には方向や位置はありません。
LightingApplet.java (一部)28 private BranchGroup createSceneGraph() { 29 BranchGroup root = new BranchGroup(); 30 31 AmbientLight light = new AmbientLight();............................(1) 32 BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0);...(2) 33 light.setInfluencingBounds(bounds);.................................(3) 34 root.addChild(light);...............................................(4) 35 36 Sphere sphere = new Sphere(0.6f); 37 root.addChild(sphere); 38 39 return root; 40 }
(1)で AmbientLight
を生成しています。色は白です。
(2)で生成した BoundingSphere
を(3)でsetInfluencingBounds()
し、(4)で BranchGroup
に addChild()
しています。
このプログラムの実行結果は次のようになります。
わかりにくいかもしれませんが、全体がうっすらと濃いグレーに照明されています。
AmbientLight
は単独で使ってもあまり意味がありません。直接光と一緒に使うべきものです。
次の例では DicertionalLight
と一緒に使っています。
LightingApplet.java (一部)28 private BranchGroup createSceneGraph() { 29 BranchGroup root = new BranchGroup(); 30 31 BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0); 32 33 DirectionalLight dlight = ┐ 34 new DirectionalLight( new Color3f(1.0f, 0.0f, 0.0f), │ 35 new Vector3f(0.87f, 0.0f, -0.5f) ); ├(1) 36 dlight.setInfluencingBounds(bounds); │ 37 root.addChild(dlight); ┘ 38 39 AmbientLight alight = new AmbientLight(); ┐ 40 alight.setInfluencingBounds(bounds); ├(2) 41 root.addChild(alight); ┘ 42 43 Sphere sphere = new Sphere(0.6f); 44 root.addChild(sphere); 45 46 return root; 47 }
(1)で DirectionalLight
の生成、作用範囲の設定、BranchGroup
への addChild()
を行っています。
(2)で AmbientLight
の生成、作用範囲設定、addChild()
を行っています。
このプログラムの実行結果は次のようになります。
直接光があたっていない部分に環境光が適用されているのがわかると思います。
javax.media.j3d.SpotLight
)javax.media.j3d.SpotLight
(スポットライト)は PointLight
サブクラスです。
SpotLight
は、光源の向きと照射角度、そして収束度を持った点光源です。
クラス宣言 public class SpotLight extends PointLight コンストラクター public SpotLight() public SpotLight(Color3f color, // 光源の色 Point3f position, // 光源の位置 Point3f attenuation, // 光の減衰係数 Vector3f direction, // 光の方向ベクトル float spreadAngle, // スポットライトの角度 float concentration) // スポットライトの収束度 public SpotLight(boolean lightOn, // 点灯しているかどうか Color3f color, // 光源の色 Point3f position, // 光源の位置 Point3f attenuation, // 光の減衰係数 Vector3f direction, // 光の方向ベクトル float spreadAngle, // スポットライトの角度 float concentration) // スポットライトの収束度
光源の色、位置、減衰係数は PointLight
と同じです。
光の方向ベクトルは、スポットライトの向きを定義します。
スポットライトの角度は、スポットライトが照らす範囲を制限します。スポットライトは、照射範囲を制限された点光源です。
収束度は、スポットライトの光がどれだけ収束するかを定義します。収束度は 0.0f〜128.0f
の範囲で指定します。128.0f
のとき最も収束度が大きくなります。
収束度のデフォルト値は 0.0f
です。0.0f
のときは、スポットライトの円錐内の輝度は一定です。
LightingApplet.java (一部)28 private BranchGroup createSceneGraph() { 29 BranchGroup root = new BranchGroup(); 30 31 BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0); 32 SpotLight light1 = 33 new SpotLight( new Color3f(1.0f, 1.0f, 1.0f), // 光源の色 34 new Point3f(0.5f, 0.5f, 0.8f), // 光源の位置 35 new Point3f(1.0f, 0.0f, 0.0f), // 光の減衰係数 36 new Vector3f(-0.5f, -0.5f, -0.7f), // 光の方向ベクトル 37 (float)(Math.PI / 7.0), // スポットライトの角度 38 0.0f ); // スポットライトの収束度 ...(1) 39 light1.setInfluencingBounds(bounds); 40 root.addChild(light1); 41 42 SpotLight light2 = 43 new SpotLight( new Color3f(1.0f, 1.0f, 1.0f), // 光源の色 44 new Point3f(-0.5f, -0.5f, 0.8f), // 光源の位置 45 new Point3f(1.0f, 0.0f, 0.0f), // 光の減衰係数 46 new Vector3f(0.5f, 0.5f, -0.7f), // 光の方向ベクトル 47 (float)(Math.PI / 7.0), // スポットライトの角度 48 20.0f ); // スポットライトの収束度 ...(2) 49 light2.setInfluencingBounds(bounds); 50 root.addChild(light2); 51 52 Sphere sphere = new Sphere(0.6f, Sphere.GENERATE_NORMALS, 100);...(3) 53 root.addChild(sphere); 54 55 return root; 56 }
(1)で球体の右上にあって球体の中心方向を照らすスポットライトを生成しています。角度は約 20度です。収束の度合はデフォルト値の 0.0f
です。
(2)で球体の左下にあって球体の中心方向を照らすもう一つのスポットライトを生成しています。収束の度合は 20.0f
にしてみました。
(3)で Sphere
を生成しています。照明の効果をスムースに見せたいので、球体の分割数を 100
に増やしています。
このプログラムの実行結果は次のようになります。
収束度を無し (0.0f
) にした左上のライトでは、ポリゴンのふちが目立っているようです。右下のライトは光が収束している効果が確認できます。
javax.media.j3d.Material
クラス)今までは光源に色を設定してみましたが、光源に色を設定するのではなく、物体の表面に光の反射の度合や色などの属性を設定することができます。
このために使用するのが javax.media.j3d.Material
クラスです。
クラス継承 java.lang.Object | +--javax.media.j3d.SceneGraphObject | +--javax.media.j3d.NodeComponent | +--javax.media.j3d.Material クラス宣言 public class Material extends NodeComponent コンストラクター public Material() public Material(Color3f ambientColor, // 環境反射による色 Color3f emissiveColor, // 発光による色 Color3f diffuseColor, // 拡散反射による色 Color3f specularColor, // 鏡面反射による色 float shininess) // 輝度
ambientColor
は、環境光による色を設定します。ambientColor
は javax.media.j3d.AmbientLight
が無いと有効になりません。
emissiveColor
は、それ自体発光している物体の発光色を設定します。emissiveColor
は光源が全く無くても有効になります。
diffuseColor
は、直接光(白色光)が物体の表面に当たったときに物体から反射される色を設定します。通常、物体の色というときには diffuseColor
のことを言っています。
specularColor
は、最も輝度が高い部分(ハイライト部分)の色です。
shininess
は、specularColor
の輝度を設定します。1.0f〜128.0f
までの値をとります。shininess
は 128.0f
のとき輝度が最大になります。輝度が最大になると、反射光による光沢は最も鋭い状態(絞られた状態)になります。
shininess
が1.0f
のとき輝度は最小になります。輝度が最小になると反射光による光沢はもっとも広がった状態になります。
Material
のデフォルト値はそれぞれ次の通りです。
lighting enable : true // 照明が有効 ambient color : (0.2, 0.2, 0.2) // 環境光による色は 濃いグレー emmisive color : (0.0, 0.0, 0.0) // 発光による色は 黒 (発光しない) diffuse color : (1.0, 1.0, 1.0) // 拡散反射による色は 白 specular color : (1.0, 1.0, 1.0) // 鏡面反射による色は 白 shininess : 64 // 輝度は 64 (中間値)
コンストラクターでは、lighitngEnable
(照明を有効にするかどうか) は、常に「有効」です。
Material
には次のようなメソッドがあり、色、輝度、照明の有効/無効がそれぞれ設定できます。設定以外に取得のメソッド (get
メソッド) もあります。
メソッド (一部) public final void setAmbientColor(Color3f color) public final void setAmbientColor(float r, // red (赤) float g, // green (緑) float b) // blue (青) public final void setEmissiveColor(Color3f color) public final void setEmissiveColor(float r, // red (赤) float g, // green (緑) float b) // blue (青) public final void setDiffuseColor(Color3f color) public final void setDiffuseColor(float r, // red (赤) float g, // green (緑) float b) // blue (青) public final void setSpecularColor(Color3f color) public final void setSpecularColor(float r, // red (赤) float g, // green (緑) float b) // blue (青) public final void setShininess(float shininess) public final void setLightingEnable(boolean state)
setLightingEnable()
メソッドで、Material
に対する照明の有効/
無効を設定できます。
javax.media.j3d.Appearance
Material
は、そのまま Shape3D
に設定することはできません。Shape3D
に設定するために必要なのが javax.media.j3d.Appearance
です。
クラス継承 java.lang.Object | +--javax.media.j3d.SceneGraphObject | +--javax.media.j3d.NodeComponent | +--javax.media.j3d.Appearance クラス宣言 public class Appearance extends NodeComponent コンストラクター public Appearance()
Shape3D
にMaterial
を設定するには次のような手順が必要です。
Material
を生成するMaterial
に必要な値を設定するAppearance
を生成する。Material
を setMaterial()
メソッドで Appearance
に設定するAppearance
を setAppearance()
メソッドで Shape3D
に設定するAppearance
はGeometry
と共に、Shape3D
に設定される重要なクラスです。Appearance
は物体の外見を決定するための重要な機能を数多く持っています。Appearance
については改めて詳しく取り上げます。
diffuseColor
)Material
の setDiffuseColor()
メソッドは、Shape3D
の物体の拡散反射による色を設定します。通常我々は、拡散反射による色を物体の色として認識しています。
MaterialTest.java (一部)28 private BranchGroup createSceneGraph() { 29 BranchGroup root = new BranchGroup(); 30 31 DirectionalLight light = 32 new DirectionalLight( new Color3f(1.0f, 1.0f, 1.0f), 33 new Vector3f(-0.5f, -0.5f, -0.7f) ); 34 BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0); 35 light.setInfluencingBounds(bounds); 36 root.addChild(light); 37 38 Material mat = new Material();..........................(1) 39 mat.setDiffuseColor( new Color3f(0.0f, 0.0f, 0.8f) );...(2) 40 Appearance ap = new Appearance();.......................(3) 41 ap.setMaterial(mat);....................................(4) 42 43 Sphere sphere = new Sphere(0.6f, ap);...................(5) 44 root.addChild(sphere); 45 46 return root; 47 }
(1)で Material
を生成しています。
(2)で setDiffuseColor()
メソッドを使って、拡散反射による色を青に設定しています。
(3)で Appearance
を生成し、(4)で setMaterial()
メソッドを使って Material
を設定しています。
(5)で Sphere
を生成しています。Sphere
のコンストラクターには Appearance
を設定できるものがあります。ここではそのコンストラクターを使って、Sphere
に Appearance
を設定しています。
このプログラムの実行結果は次のようになります。
斜めから白色光で照明された球体が、青色になっているのがわかると思います。
球体の最も明るい部分 (ハイライト) は白く光っています。この色は setSpecularColor()
で設定することができます。
物体の暗い部分は黒になっています。暗い部分の色は setAmbientColor()
メソッドで設定することができます。。
ambientColor
)環境光による色は、周囲に反射した間接光で照明された物体の色です。
Java 3D では、環境光による色はjavax.media.j3d.AmbientLight
が無いと描画されません。
MaterialTest.java (一部)28 private BranchGroup createSceneGraph() { 29 BranchGroup root = new BranchGroup(); 30 31 BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0); 32 33 DirectionalLight dlight = 34 new DirectionalLight( new Color3f(1.0f, 1.0f, 1.0f), 35 new Vector3f(-0.5f, -0.5f, -0.7f) ); 36 dlight.setInfluencingBounds(bounds); 37 root.addChild(dlight); 38 39 AmbientLight alight = new AmbientLight(); ┐ 40 alight.setInfluencingBounds(bounds); ├(1) 41 root.addChild(alight); ┘ 42 43 Material mat = new Material();..........................(2) 44 mat.setDiffuseColor( new Color3f(0.0f, 0.0f, 0.8f) ); 45 mat.setAmbientColor( new Color3f(0.2f, 0.0f, 0.0f) );...(3) 46 Appearance ap = new Appearance();.......................(4) 47 ap.setMaterial(mat);....................................(5) 48 49 Sphere sphere = new Sphere(0.6f, ap);...................(6) 50 root.addChild(sphere); 51 52 return root; 53 }
(1)で AmbientLight
を生成して BranchGroup
に addChild()
しています。
(2)で Material
を生成し、(3)で setAmbientColor()
メソッドを使って暗い赤色を設定しています。
(4)で Appearance
を生成し、(5)で setMaterial()
メソッドで Material
を設定しています。
(6)でコンストラクターの引数に Appearance
を指定して Sphere
を生成しています。
このプログラムの実行結果は次のようになります。
直接光で照明されている部分は青色になっています。直接光で照明されていない部分は暗い赤色になっているのがわかると思います。
shininess
)setShininess()
メソッドは、鏡面反射による物体のハイライト部分の強度を設定します。
輝度を変化してテストできるように、アプレットに java.awt.TextField
と java.awt.Scrollbar
を追加してみました。はじめにプログラムの実行結果を見てください。
TextField
に shininess
を数値入力して
[ENTER]
キーを押すか、Scrollbar
をスライ
ドさせて shininess
を変化させることができます。
輝度は1.0〜128.0
の範囲で変化します。
128.0
のとき輝度が最大です。
このとき光が最も収束した状態になり、光束が細くなります。
1.0
のとき輝度が最小です。
このとき光が最も拡散した状態になり、光束が太くなります。
このプログラムのソースは次の通りです。
MaterialTest.java1 // Java 3D Test Applet 2 // MaterialTest.java 3 // Copyright (c) 1999 ENDO Yasuyuki 4 // mailto:yasuyuki@javaopen.org 5 // http://www.javaopen.org/j3dbook/index.html 6 7 import java.applet.*; 8 import java.awt.*; 9 import java.awt.event.*; 10 import javax.media.j3d.*; 11 import javax.vecmath.*; 12 import com.sun.j3d.utils.applet.MainFrame; 13 import com.sun.j3d.utils.universe.SimpleUniverse; 14 import com.sun.j3d.utils.geometry.Sphere; 15 16 public class MaterialTest extends Applet { 17 private Material material = new Material();.......................................(1) 18 19 private Scrollbar scrollbar = new Scrollbar(Scrollbar.HORIZONTAL, 1, 1, 1, 129); ┬(2) 20 private TextField textField = new TextField("1.0"); ┘ 21 22 23 public MaterialTest() { 24 this.setLayout(new BorderLayout()); 25 26 Panel panel = new Panel(); 27 panel.setLayout(new BorderLayout()); 28 this.add(panel, BorderLayout.SOUTH); 29 30 scrollbar.addAdjustmentListener( new AdjustmentListener() { ┐ 31 public void adjustmentValueChanged(AdjustmentEvent e) { │ 32 float value = (float)e.getValue();...................(4) │ 33 material.setShininess(value); ├(3) 34 textField.setText( Float.toString(value) ); │ 35 } │ 36 }); │ 37 panel.add(scrollbar, BorderLayout.CENTER); ┘ 38 39 textField.addActionListener(new ActionListener() { ┐ 40 public void actionPerformed(ActionEvent e) { │ 41 float value = 1.0f; │ 42 try { │ 43 value = Float.parseFloat(e.getActionCommand()); ┐ │ 44 if (value < 1.0f) value = 1.0f; ├(6) │ 45 if (value > 128.0f) value = 128.0f; ┘ ├(5) 46 material.setShininess(value);.....................(7) │ 47 scrollbar.setValue( (int)value ); │ 48 System.out.println("value=" + value); │ 49 } catch (NumberFormatException ex) {} │ 50 } │ 51 }); ┘ 52 panel.add(textField, BorderLayout.WEST); 53 54 GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration(); 55 Canvas3D canvas = new Canvas3D(config); 56 add(canvas, BorderLayout.CENTER); 57 58 SimpleUniverse universe = new SimpleUniverse(canvas); 59 universe.getViewingPlatform().setNominalViewingTransform(); 60 61 BranchGroup scene = createSceneGraph(); 62 universe.addBranchGraph(scene); 63 } 64 65 private BranchGroup createSceneGraph() { 66 BranchGroup root = new BranchGroup(); 67 68 DirectionalLight light = 69 new DirectionalLight( new Color3f(1.0f, 1.0f, 1.0f), 70 new Vector3f(-0.5f, -0.5f, -0.7f) ); 71 BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0); 72 light.setInfluencingBounds(bounds); 73 root.addChild(light); 74 75 material.setCapability(Material.ALLOW_COMPONENT_WRITE);......(8) 76 material.setDiffuseColor( new Color3f(0.0f, 0.0f, 0.8f) );...(9) 77 material.setShininess(1.0f); 78 Appearance ap = new Appearance(); 79 ap.setMaterial(material); 80 81 Sphere sphere = new Sphere(0.6f, ap); 82 root.addChild(sphere); 83 84 return root; 85 }
(1)でMaterial
型の private
変数 material
を確保し、Material
オブジェクトを生成してこの変数に代入しています。material
は TextFiled, Scrollbar
で輝度を変化させるときに使用します。
(2)で ScrollBar, TextFiled
を生成しています。
(3)は Scrollbar
の値が変化したときに使用するイベント処理を記述しています。
イベント処理の記述についての詳しい説明は省きますが、(4)でMaterial
の setShininess()
メソッドを使用しています。ここでは Scrollbar
の変化量を輝度として設定しています。
(5)では TextField
への入力で [ENTER]
キーが押されたときのイベント処理を記述しています。
(6)で TextField
に入力された文字列を float
の値に変換しています。
(7)で setShininess()
メソッドを使って、入力値を輝度として設定しています。
(8)で setCapability()
メソッドを使って、定数値 Material.ALLOW_COMPONENT_WRITE
を設定しています。
実行時に値を変更する場合などには、CapabilityBit
というものをあらかじめ設定しておく必要があります。このために使用するのが setCapability()
メソッドです。これについては後で詳しく説明します。
(9)で setShininess()
メソッドで輝度を 1.0f
に設定しています。
このプログラムを実行して、輝度をいろいろ変化させてみてください。shininess
のデフォルト値の 64.0
ではどうなるでしょうか?
speculerColor
)setSpecularColor()
メソッドは、物体の一番輝度の高い部分 (ハイライト部分) の鏡面反射による色を設定します。
MaterialTest.java (一部)28 private BranchGroup createSceneGraph() { 29 BranchGroup root = new BranchGroup(); 30 31 DirectionalLight light = 32 new DirectionalLight( new Color3f(1.0f, 1.0f, 1.0f), 33 new Vector3f(-0.5f, -0.5f, -0.7f) ); 34 BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0); 35 light.setInfluencingBounds(bounds); 36 root.addChild(light); 37 38 Material mat = new Material(); 39 mat.setDiffuseColor( new Color3f(0.0f, 0.0f, 0.8f) ); 40 mat.setSpecularColor( new Color3f(1.0f, 0.0f, 0.0f) );...(1) 41 //mat.setShininess(1.0f); 42 mat.setShininess(12.0f);.................................(2) 43 //mat.setShininess(128.0f); 44 Appearance ap = new Appearance(); 45 ap.setMaterial(mat); 46 47 Sphere sphere = new Sphere(0.6f, ap); 48 root.addChild(sphere); 49 50 return root; 51 }
(1)で setSpecularColor()
メソッドを使って、鏡面反射による色を赤に設定しています。
(2)で輝度を 12.0f
に設定しています。
このプログラムの実行結果は次のようになります。
今回はハイライト部分を強調したかったので赤色を設定しました。
emissiveColor
)setEmissiveColor() メソッドは、それ自体発光している物体の発光色を設定します。
MaterialTest.java (一部)28 private BranchGroup createSceneGraph() { 29 BranchGroup root = new BranchGroup(); 30 31 //DirectionalLight light = ┐ 32 // new DirectionalLight( new Color3f(1.0f, 1.0f, 1.0f), │ 33 // new Vector3f(-0.5f, -0.5f, -0.7f) ); ├(1) 34 //BoundingSphere bounds = new BoundingSphere(newPoint3d(), 100.0); │ 35 //light.setInfluencingBounds(bounds); │ 36 //root.addChild(light); ┘ 37 38 Material mat = new Material(); 39 mat.setEmissiveColor( new Color3f(0.0f, 0.0f, 0.8f) );...(2) 40 Appearance ap = new Appearance(); 41 ap.setMaterial(mat); 42 43 Sphere sphere = new Sphere(0.6f, ap); 44 root.addChild(sphere); 45 46 return root; 47 }
(1)は DirectionalLight
の生成から addChild()
までを行っていますが、すべてコメントにしてあります。つまり、このプログラムでは光源を使用していません。
(2)でsetEmissiveColor()
メソッドで発光色を青に設定しています。
このプログラムの実行結果は次のようになります。
全体が青色になって、それ自体発光しているような感じになっています。球体の細部はよくわかりません。
Material
のまとめMaterial
で設定できる色、輝度をすべて使ったサンプルを紹介します。
左上の TextField, Scrollbar
で diffuseColor
を変更できます。上から r (赤), g (緑), b (青)
です。
右上の TextField, Scrollbar
で emissiveColor
を変更できます。上から r (赤), g (緑), b (青)
です。
左下の TextField, Scrollbar
で specularColor, shininess
を変更できます。上から r (赤), g (緑), b (青), shininess (輝度)
です。
右下の TextField, Scrollbar, Checkbox
で ambientColor
を変更できます。上から r (赤), g (緑), b (青)
です。一番下の Checkbox
を使って AmbientLight
を ON/OFF できます。
このプログラムのソースは次の通りです。GUIの構築が大半なので、詳しい解説は省略します。
さてこれまで、法線ベクトルについて触れずに説明して来ました。
法線ベクトルとは何でしょうか?
光源がポリゴンを照明するとき、ポリゴンの表面の「明るさ」を計算するためには、ポリゴンの「向き」についての情報が必要になります。
ポリゴンの「向き」を設定するために、法線ベクトルを使用します。
Java 3D では、頂点ごとに法線ベクトルを設定します。法線ベクトルを設定するために、javax.media.GeometryArray
には setNormal(), setNormals()
があります。
法線ベクトルを設定するメソッド (javax.media.j3d.GeometryArray) public final void setNormal(int index, // 法線を設定する頂点 index float[] normal) // 法線ベクトル { x, y, z } public final void setNormal(int index, // 法線を設定する頂点 index Vector3f normal) // 法線ベクトル public final void setNormals(int index, // 法線の設定を開始する頂点 index float[] normals) // 法線ベクトル配列 { x0, y0, z0, x1, y1, z1, ... } public final void setNormals(int index, // 法線の設定を開始する頂点 index Vector3f[] normals) // 法線ベクトル配列 { n0, n1, ... } public final void setNormals(int index, // 法線の設定を開始する頂点 index float[] normals, // 法線ベクトル配列 { x0, y0, z0, x1, y1, z1, ... } int start, // 設定を開始する法線の index int length) // 設定を終了する法線の index public final void setNormals(int index, // 法線の設定を開始する頂点 index Vector3f[] normals, // 法線ベクトル配列 { n0, n1, ... } int start, // 設定を開始する法線の index int length) // 設定を終了する法線の index
法線ベクトルを設定する場合、GeometryArray
は次のような手順で使用します。
GeometryArray.NORMALS
を指定して GeometryArray
を生成するsetVertices()
メソッドで頂点配列を設定するsetNormals()
メソッドで法線ベクトルを設定するGeometryArray
を指定して Shape3D
を生成するShape3D
を addChild()
する今までと違う点は、GeometryArray
のコンストラクターで GeometryArray.NORMALS
を指定する点と、setNormals()
メソッドで法線ベクトルを指定する点の 2点です。
法線ベクトルは照明効果にどのような影響を与えるでしょうか。
三角形ポリゴンの頂点に、それぞれ異なった法線ベクトルを設定して確かめてみましょう。
LineArray
を使って視覚化してみる以上のような前提でプログラミングしてみます。
NormalTest.java (一部)54 private BranchGroup createSceneGraph() { 55 BranchGroup root = new BranchGroup(); 56 root.setCapability(BranchGroup.ALLOW_DETACH); 57 58 root.addChild( createLight() ); 59 60 Point3d[] vertices = new Point3d[3]; 61 62 vertices[0] = new Point3d(-0.4, -0.8, 0.0); // 左下の頂点 63 vertices[1] = new Point3d(0.5, -0.4, 0.0); // 右下の頂点 64 vertices[2] = new Point3d(-0.6, 0.7, -0.4); // 左上の頂点 65 66 Vector3f[] normals = new Vector3f[3]; ┐ 67 │ 68 normals[0] = new Vector3f(0.0f, 0.0f, 1.0f); // 視点に垂直 ├(1) 69 normals[1] = new Vector3f(0.1f, 0.1f, 0.89f); // ほぼ視点に垂直 │ 70normals[2] = new Vector3f(-0.63f, 0.63f, 0.1f); // かなり斜め ┘ 71 72 TriangleArray geometry = 73 new TriangleArray( vertices.length, 74 GeometryArray.COORDINATES | 75 GeometryArray.NORMALS);...........(2) 76 geometry.setCoordinates(0, vertices);..................(3) 77 geometry.setNormals(0, normals);.......................(4) 78 79 Shape3D shape = new Shape3D(geometry); 80 shape.setAppearance( createAppearance() ); 81 82 root.addChild(shape); 83 84 NormalRender nrender = new NormalRender(geometry, 0.5f); // 法線を視覚化 ...(5) 85 nshape = new Shape3D( nrender.getLineArray() );..........................(6) 86 root.addChild(nshape);...................................................(7) 87 88 return root; 89 } 90 91 private Light createLight() { 92 DirectionalLight light = 93 new DirectionalLight( new Color3f(1.0f, 1.0f, 1.0f), 94 new Vector3f(0.0f, 0.0f, -1.0f) ); // ヘッドライト 95 BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0); 96 light.setInfluencingBounds(bounds); 97 98 return light; 99 } 100 101 private Appearance createAppearance() { 102 Material mat = new Material(); 103 mat.setDiffuseColor( new Color3f(0.0f, 0.0f, 1.0f) ); 104 mat.setShininess(128.0f); 105 106 Appearance ap = new Appearance(); 107 ap.setMaterial(mat); 108 109 return ap; 110 }
(1)で法線ベクトルを生成し、各要素を初期化しています。左下の頂点には視点に垂直な法線、右下の頂点には視点にほぼ垂直な法線、左上の頂点にはかなり斜めな法線を設定しました。
(2)で TraingleArray
を生成しています。2番目の引数には、GeometryArray.COORDINATES
と OR して GeometryArray.NORMALS
を指定しています。
(3)で setNormals()
メソッドを使って法線ベクトルを設定しています。
(4)で法線ベクトルを視覚化するために、NormalRender
を生成して使用しています。この NormalRender
クラスは法線を視覚化する目的で書いたものです。各頂点から法線ベクトル方向に引かれた線分を生成します。
(5)で NormalRender
を生成しています。最初の引数は、法線の視覚化の対象となる GeometryArray
です。2番目の引数は法線ベクトルを視覚化したときのスケールです。法線ベクトルの長さが 1.0
では視野に収まらないので 0.5
倍しています。
(6)でNormalRender
の getLineArray()
メソッドを使って、法線ベクトルから生成された LineArray
を取得しています。取得した LineArray
をコンストラクター引数にして Shape3D
を生成しています。
生成した Shape3D
を(7)でaddChild()
しています。
このプログラムの実行結果は次のようになります。
チェックボックスを押すと、法線ベクトルを視覚化した LineArray
を削除したり追加したりすることができます。
チェックボックスが押されたときに、法線ベクトルを視覚化した LineArray
を動的に追加/削除するには次のようにしました。
追加は次のようにします。
Group#numChildren()
で"子"ノードの数を取得してfor
ループを開始Group#getChild(int)
で"子"ノードを取得し、追加したいノードと比較するGroup#addChild(Node)
で追加するここでは次のようなメソッドとして独立させました。
112 private void addChild(Group group, Node node) { 113 for (int i=0; i<group.numChildren(); i++) if (node == group.getChild(i)) return; 114 group.addChild(node); 115 }
削除は次のようにします。
Group#numChildren()
で"子"ノードの数を取得してfor
ループを開始Group#getChild(int)
で"子"ノードを取得し、追加したいノードと比較するGroup#removeChild(int)
で削除するここでは次のようなメソッドとして独立させました。
117 private void removeChild(Group group, Node node) { 118 for (int i=0; i<group.numChildren(); i++) { 119 if (node == group.getChild(i)) group.removeChild(i); 120 } 121 }
ツリーが"live"状態のときにはノードを動的に追加/削除できないので、次のような方法でツリーを一時的に切り離します。
Locale#removeBranchGraph(BranchGroup)
でツリーを切り離すLocale#addBranchGraph(BranchGroup)
でツリーを再接続するここでは次のように処理しました。
26 Checkbox check = new Checkbox("Normals", true); 27 check.addItemListener( new ItemListener() { 28 public void itemStateChanged(ItemEvent e) { 29 int state = e.getStateChange(); 30 universe.getLocale().removeBranchGraph(scene); 31 if (state == ItemEvent.SELECTED) { 32 addChild(scene, nshape); 33 } else if (state == ItemEvent.DESELECTED) { 34 removeChild(scene, nshape); 35 } 36 universe.addBranchGraph(scene); 37 } 38 }); 39 panel.add(check);
SimpleUniverse#getLocale()
でLocale
を取得し、removeBranchGraph()
を実行しています。
チェックボックスの状態に応じて追加/削除を行い、SimpleUniverse#addBranchGraph()
でツリーを再接続しています。
左下の頂点の法線ベクトルは視点 (と光源) に垂直なので、白く反射しているのがわかると思います。
右下の頂点の法線ベクトルは視点 (と光源) にぼほ垂直なので、明るい青色になっています。
左上の頂点の法線ベクトルはかなり斜めなので、暗い青色になっています。
頂点間では明るさがスムースに補間されています。
NormalRender
のソースは次の通りです。
NormalRender.java1 // Java 3Dテスト用プログラム 2 // NormalRedner.java 3 // Copyright (c) 1999 ENDO Yasuyuki 4 // mailto:yasuyuki@javaopen.org 5 // http://www.javaopen.org/j3dbook/index.html 6 7 import javax.media.j3d.*; 8 import javax.vecmath.*; 9 10 public class NormalRender { 11 private LineArray nline = null; 12 public NormalRender(GeometryArray geom) { this(geom, 1.0f); } 13 public NormalRender(GeometryArray geom, float scale) { 14 Point3f[] vertices = new Point3f[geom.getVertexCount()]; 15 Vector3f[] normals = new Vector3f[geom.getVertexCount()]; 16 for (int i=0; i<geom.getVertexCount(); i++) { 17 vertices[i] = new Point3f(); 18 normals[i] = new Vector3f(); 19 } 20 geom.getCoordinates(0, vertices); 21 geom.getNormals(0, normals); 22 Point3f[] nvertices = new Point3f[vertices.length * 2]; 23 int n = 0; 24 for (int i=0; i<vertices.length; i++ ){ 25 nvertices[n++] = new Point3f( vertices[i] ); 26 nvertices[n++] = new Point3f( vertices[i].x + scale * normals[i].x, 27 vertices[i].y + scale * normals[i].y, 28 vertices[i].z + scale * normals[i].z ); 29 } 30 nline = new LineArray(nvertices.length, GeometryArray.COORDINATES); 31 nline.setCoordinates(0, nvertices); 32 } 33 34 public LineArray getLineArray() { return nline; } 35 }
詳しい解説は省略しますが、コンストラクターの引数として受け取った GeometryArray
から頂点配列と法線ベクトルを取り出し、それを元にして LineArray
を生成しています。
メソッドは getLineArray()
だけです。法線ベクトルから生成した LineArray
を返します。
前の例では、任意の法線ベクトルを設定してみました。法線ベクトルを計算する方法にはどんなものがあるでしょうか。
平面の法線ベクトルを計算するには、平面上の 3点の座標からベクトルの外積を求める方法があります。
平面上の 3点の座標をそれぞれ (x0, y0, z0), (x1, y1, z1), (x2, y2, z2)
とすると、
ax = x2 - x1 ay = y2 - y1 az = z2 - z1 bx = x0 - x1 by = y0 - y1 bz = z0 - z1
外積は次のように求められます。
nx = ay・bz - az・by ny = az・bx - ax・bz nz = ax・by - ay・bx
求めた外積 (法線ベクトル) を正規化します。
length = √nx2 + ny2 + nz2 Nx = nx / length Ny = ny / length Nz = nz / length
javax.vecmath.Vector3f
には normalize()
というメソッドがあり、ベクトルを正規化することができます。
NomralTest.java (一部)54 private BranchGroup createSceneGraph() { 55 BranchGroup root = new BranchGroup(); 56 root.setCapability(BranchGroup.ALLOW_DETACH); 57 58 root.addChild( createLight() ); 59 60 Point3d[] vertices = new Point3d[3]; 61 62 vertices[0] = new Point3d(-0.4, -0.8, 0.0); 63 vertices[1] = new Point3d(0.5, -0.4, 0.0); 64 vertices[2] = new Point3d(-0.6, 0.7, -0.4); 65 66 Vector3f faceNormal = calcFaceNormal(vertices); // 面法線の計算 ...(1) 67 68 Vector3f[] normals = new Vector3f[3];...........................(2) 69 70 for (int i=0; i<normals.length; i++) normals[i] = faceNormal;...(3) 71 72 TriangleArray geometry = 73 new TriangleArray( vertices.length, 74 GeometryArray.COORDINATES | 75 GeometryArray.NORMALS); 76 geometry.setCoordinates(0, vertices); 77 geometry.setNormals(0, normals); 78 79 Shape3D shape = new Shape3D(geometry); 80 shape.setAppearance( createAppearance() ); 81 82 root.addChild(shape); 83 84 NormalRender nrender = new NormalRender(geometry, 0.5f); 85 nshape = new Shape3D(nrender.getLineArray()); 86 root.addChild(nshape); 87 88 return root; 89 } : : 101 private Vector3f calcFaceNormal(Point3d[] vertices) { ...(4) 102 double ax = vertices[2].x - vertices[1].x; 103 double ay = vertices[2].y - vertices[1].y; 104 double az = vertices[2].z - vertices[1].z; 105 double bx = vertices[0].x - vertices[1].x; 106 double by = vertices[0].y - vertices[1].y; 107 double bz = vertices[0].z - vertices[1].z; 108 109 double nx = ay * bz - az * by; 110 double ny = az * bx - ax * bz; 111 double nz = ax * by - ay * bx; 112 113 Vector3f normal = new Vector3f( (float)nx, 114 (float)ny, 115 (float)nz ); 116 normal.normalize(); 117 118 //DEBUG 119 System.out.println("nx=" + normal.x + ", ny=" + normal.y + ", nz=" + normal.z); 120 121 return normal; 122 }
(1)で calcFaceNormal()
メソッドを使って法線ベクトルを計算しています。
(2)で法線ベクトルの配列を確保し、(3)ですべての頂点に同じ法線ベクトルを設定しています。
(4)は外積を使って面法線を計算する calcFaceNormal()
メソッドです。平面上の 3点の頂点配列を引数にとり、法線ベクトルを返します。
このプログラムの実行結果は次のようになります。
平面が同じ青色に描画されているのがわかると思います。
ポリゴンがひとつだと立体的な描画効果がよくわからないので、複数のポリゴンの面法線を計算して描画してみましょう。
この例では、ポリゴン数 = 32 の複数ポリゴンについて面法線を計算し、描画しています。
法線ベクトルを視覚化すると次のようになります。
このプログラムのソースは次の通りです。
NormalTest.java (一部)55 private BranchGroup createSceneGraph() { 56 BranchGroup root = new BranchGroup(); 57 root.setCapability(BranchGroup.ALLOW_DETACH); 58 59 root.addChild( createLight() ); 60 61 Point3d[] vertices = new Point3d[96]; ┐ 62 │ 63 vertices[0] = new Point3d(-0.4, 0.4, -0.7); │ 64 vertices[1] = new Point3d(-0.4, 0.2, -0.4); │ 65 vertices[2] = new Point3d(-0.2, 0.4, -0.4); ├(1) : │ : │ 205 vertices[93] = new Point3d( 0.4, -0.2, -0.4); │ 206 vertices[94] = new Point3d( 0.2, -0.4, -0.4); │ 207 vertices[95] = new Point3d( 0.4, -0.4, -0.7); ┘ 208 209 Vector3f[] normals = calcFaceNormals(vertices);...(2) 210 211 TriangleArray geometry = 212 new TriangleArray( vertices.length, 213 GeometryArray.COORDINATES | 214 GeometryArray.NORMALS); 215 geometry.setCoordinates(0, vertices); 216 geometry.setNormals(0, normals); 217 218 Shape3D shape = new Shape3D(geometry); 219 shape.setAppearance( createAppearance() ); 220 221 root.addChild(shape); 222 223 NormalRender nrender = new NormalRender(geometry, 0.2f); 224 nshape = new Shape3D(nrender.getLineArray()); 225 root.addChild(nshape); 226 227 return root; 228 } : : 240 private Vector3f[] calcFaceNormals(Point3d[] vertices) { ...(3) 241 Vector3f[] normals = new Vector3f[vertices.length]; 242 243 for (int i=0; i<vertices.length; i+=3) { 244 Vector3f fnormal = calcFaceNormal(i, vertices); 245 normals[i] = fnormal; 246 normals[i + 1] = fnormal; 247 normals[i + 2] = fnormal; 248 } 249 250 return normals; 251 } 252 253 private Vector3f calcFaceNormal(int index, Point3d[] vertices) { ...(4) 254 double ax = vertices[index + 2].x - vertices[index + 1].x; 255 double ay = vertices[index + 2].y - vertices[index + 1].y; 256 double az = vertices[index + 2].z - vertices[index + 1].z; 257 double bx = vertices[index + 0].x - vertices[index + 1].x; 258 double by = vertices[index + 0].y - vertices[index + 1].y; 259 double bz = vertices[index + 0].z - vertices[index + 1].z; 260 261 double nx = ay * bz - az * by; 262 double ny = az * bx - ax * bz; 263 double nz = ax * by - ay * bx; 264 265 Vector3f normal = new Vector3f( (float)nx, 266 (float)ny, 267 (float)nz ); 268 normal.normalize(); 269 270 //DEBUG 271 System.out.println("nx=" + normal.x + ", ny=" + normal.y + ", nz=" + normal.z); 272 273 return normal; 274 }
(1)で頂点配列を確保し、各頂点の座標を初期化しています。
(2)で calcFaceNormals()
メソッドを使って各ポリゴンの法線ベクトルを計算しています。
(3)は複数ポリゴンの法線ベクトルを計算する calcFaceNormals()
メソッドです。
(4)はひとつのポリゴンの法線ベクトルを計算する calcFaceNormal()
メソッドです。
実行結果でわかると思いますが、複数ポリゴンで形成された物体をスムースに描画するには、面法線を計算しただけでは不十分のようです。
法線を平均してスムースに描画するには、次のような方法があります。
GeometryInfo
とNormaiGenerator
)Java 3D には法線ベクトルを計算してくれる便利なクラスが用意されています。com.sun.j3d.unils.geometry.GeometryInfo
と com.sun.j3d.utils.geometry.NormalGenerator
です。
クラス宣言 public class GeometryInfo extends java.lang.Object コンストラクター public GeometryInfo(int primitive) フィールド public static final int TRIANGLE_ARRAY // TriangleArray public static final int QUAD_ARRAY // QuadArray public static final int TRIANGLE_FAN_ARRAY // TriangleFanArray public static final int TRIANGLE_STRIP_ARRAY // TriangleStripArray public static final int POLYGON_ARRAY // 複数等高線で形成された面
GeometryInfo
のコンストラクターでは、GeometryInfo
のフィールドで定義された定数値を指定します。
定数値はそれぞれ TriangleArray, QuarArray, TriangleFanArray, TriangleStripArray
などに対応しています。のうち、どの GeometryArray
を使うのかを指定します。
POLYGON_ARRAY
を指定したとき、複数の等高線から形成された面を定義することができます。
メソッド (一部) public void setCoordinates(Point3f[] coordinates) public void setCoordinates(Point3d[] coordinates)
GeometryInfo
を生成し、setCoordinates()
メソッドで頂点配列を設定します。コンストラクターの引数で指定した GeometryArray
が生成されます。
メソッド (一部) public GeometryArray getGeometryArray() public IndexedGeometryArray getIndexedGeometryArray(boolean compact)
生成された GeometryArray
は、getGeometryArray(), getIndexedGeomeryArray()
メソッドを使って取得することができます。
ここでは詳しく説明しませんが、GeometryInfo
はこの他にも便利な機能を数多く持っています。
GeometryInfo
を元に、頂点法線を計算するクラスが com.sun.j3d.utils.geometry.NormalGenerator です。
クラス宣言 public class NormalGenerator extends java.lang.Object コンストラクター public NormalGenerator() メソッド (一部) public void generateNormals(GeometryInfo geom)
generateNormals()
メソッドは、引数で指定された GeometryInfo
の頂点法線を計算し、GeometryInfo
に計算した法線をセットします。
GeometryInfo
と NormalGenerator
の使い方をまとめると次のようになります。
GeometryInfo
を生成するGeometryInfo
の setCoordinates()
メソッドで頂点配列を設定するNormalGenerator
を生成するNormalGererator
の generateNormals()
メソッドで法線ベクトルを生成する。生成された法線ベクトルは NormalGenerator
にセットされるGeometryInfo
getGeometry()
メソッドで Geometry
を取得し、取得した Geometry
を元に Shape3D
を生成するShape3D
addChild()
するでは、前の例と全く同じ複数ポリゴンの頂点法線を計算して描画してみましょう。
NormalTest.java (一部)57 private BranchGroup createSceneGraph() { 58 BranchGroup root = new BranchGroup(); 59 root.setCapability(BranchGroup.ALLOW_DETACH); 60 61 root.addChild( createLight() ); 62 63 Point3d[] vertices = new Point3d[96]; 64 65 vertices[0] = new Point3d(-0.4, 0.4, -0.7); 66 vertices[1] = new Point3d(-0.4, 0.2, -0.4); 67 vertices[2] = new Point3d(-0.2, 0.4, -0.4); : : 207 vertices[93] = new Point3d( 0.4, -0.2, -0.4); 208 vertices[94] = new Point3d( 0.2, -0.4, -0.4); 209 vertices[95] = new Point3d( 0.4, -0.4, -0.7); 210 211 GeometryInfo ginfo = new GeometryInfo(GeometryInfo.TRIANGLE_ARRAY);...(1) 212 ginfo.setCoordinates(vertices);.......................................(2) 213 214 NormalGenerator gen = new NormalGenerator();..........................(3) 215 gen.generateNormals(ginfo);...........................................(4) 216 217 Shape3D shape = new Shape3D(ginfo.getGeometryArray()); 218 shape.setAppearance( createAppearance() ); 219 220 root.addChild(shape); 221 222 NormalRender nrender = new NormalRender(ginfo.getGeometryArray(), 0.2f); 223 nshape = new Shape3D(nrender.getLineArray()); 224 root.addChild(nshape); 225 226 return root; 227 }
(1)で GeometryInfo.TRAINGLE_ARRAY
を指定して GeometryInfo
を生成しています。
(2)で setCoordinates()
メソッドを使って頂点配列を設定しています。
(3)で NormalGererator
を生成し、(4)で generateNormals()
メソッドを使って頂点法線を計算して GeometryInfo
にセットしています。
このプログラムの実行結果は次のようになります。
実際には曲面ではないのですが、頂点法線がうまく平均化されているためになめらかな曲面のように見えます。
NormalGenerator
で生成された頂点法線はスムースに平均化されています。
法線ベクトルを視覚化すると次のようになります。
各頂点の法線ベクトルが平均化されているのが分かると思います。
javax.media.j3d.Fog
)javax.media.j3d.Fog
を使うと、霧のように遠くがかすんで行く効果が得られます。
クラス継承 java.lang.Object | +--javax.media.j3d.SceneGraphObject | +--javax.media.j3d.Node | +--javax.media.j3d.Leaf | +--javax.media.j3d.Fog | +--javax.media.j3d.ExponentialFog | +--javax.media.j3d.LinearFog クラス宣言 public abstract class Fog extends Leaf コンストラクター public Fog() public Fog(Color3f color) // 霧の色 public Fog(float r, // 霧の赤色値 float g, // 霧の緑色値 float b) // 霧の青色値 メソッド (一部) public final void setInfluencingBounds(Bounds region) // 霧が有効になる領域 public final void setInfluencingBoundingLeaf(BoundingLeaf region) // 霧が有効になる領域リーフ
Fog
は抽象クラスで、実際に使われるのはサブクラスの ExponentiolFog, LinearFog
です。
Fog
のコンストラクターでは霧の色が設定できます。デフォルト値は (0.0, 0.0, 0.0)
(黒) です。
Fog
は、それが有効になる領域を設定しないと適用されません。これを設定するためのメソッドが setInfluencingBounds(), setInfluencingBoundingLeaf()
です。setInfluencingBounds()
では javax.media.j3d.Bounds
のサブクラス、setInfluencingBoundingLeaf()
では javax.media.j3d.BoundingLeaf
を指定します。
Fog
の使い方は次のようになります。
Fog
を生成するFog
が有効になる領域を設定するFog
をシーングラフに addChild()
するjavax.media.j3d.BoundingLeaf
BoundingLeaf
は他の Bounds
を参照する Leaf
ノードです。
BoundingLeaf のクラス継承 java.lang.Object | +--javax.media.j3d.SceneGraphObject | +--javax.media.j3d.Node | +--javax.media.j3d.Leaf | +--javax.media.j3d.BoundingLeaf クラス宣言 public class BoundingLeaf extends Leaf コンストラクター public BoundingLeaf() public BoundingLeaf(Bounds region) // 参照先の Bounds オブジェクト
BoundingLeaf
のコンストラクターでは参照先の Bounds
オブジェクトを指定します。
BoundingLeaf
は javax.media.j3d.Leaf
のサブクラスなので、addChild()
メソッドを使ってシーングラフに追加します。
javax.media.j3d.ExponentialFog
javax.media.j3d.ExponentialFog
は、霧の強度を設定し、距離に応じた指数関数で霧の効果を計算して霧を描画します。
クラス宣言 public class ExponentialFog extends Fog コンストラクター (一部) public ExponentialFog(Color3f color, // 霧の色 float density) // 霧の強度 public ExponentialFog(float r, // 霧の赤色値 float g, // 霧の緑色値 float b, // 霧の青色値 float density) // 霧の強度
ExponentialFog
のコンストラクターでは、霧の色と霧の強度を指定できます。
ExponentialFog
の霧の効果は次の式で計算されます。
f = e-d・z f : 霧の係数 d : 霧の強度 z : 視点からピクセルまでの距離 C' = C・f + Cf・(1-f) C : 霧の適用対象となるピクセルの色 Cf : 霧の色
javax.media.j3d.LinearFog
javax.media.j3d.LinearFog
は、霧の適用が開始される前方距離と、霧で完全に隠される後方距離を指定して霧を描画します。
クラス宣言 public class LinearFog extends Fog コンストラクター (一部) public LinearFog(Color3f color, // 霧の色 double frontDistance, // 霧の効果が開始される前方距離 double backDistance) // 霧の効果が終了する後方距離 public LinearFog(float r, // 霧の赤色値 float g, // 霧の緑色値 float b, // 霧の青色値 double frontDistance, // 霧の効果が開始される前方距離 double backDistance) // 霧の効果が終了する後方距離
LinearFog
のコンストラクターでは、霧の色と、霧の適用が開始される前方距離と、霧で完全に覆い隠される後方距離を指定できます。
LinearFog
による霧の効果は次の式で計算されます。
B - z f = ------- B - F B : 霧が適用される前方距離 F : 霧で完全に覆い隠される後方距離 z : 視点からピクセルまでの距離 C' = C・f + Cf・(1-f) C : 霧の適用対象となるピクセルの色 Cf : 霧の色
ExponentialFog, LinearFog
を使ったサンプルの実行結果を見てください。
物体の diffuseColor
、ファークリップ面までの距離が変更できます。
Checkbox
で ExponentialFog, LinearFog,
または霧が無い状態を切替えられます。
ExponentialFog
のときは霧の強度を、LinearFog
のときは前方距離、後方距離を入力できます。
霧の色はデフォルト値の黒になっています。霧の色も TextField
に RGB を入力して変更出来ます。
サンプルのソースは次の通りです。
FogTest.java (一部)287 private BranchGroup createSceneGraph() { 288 BranchGroup root = new BranchGroup(); 289 root.setCapability(BranchGroup.ALLOW_DETACH); 290 291 root.addChild(createLight(0.3f, -0.3f, -0.3f)); 292 293 BoundingSphere bounds = new BoundingSphere(new Point3d(), 10000.0); 294 295 app = createAppearance(); 296 297 double x = -2.0; ┐ 298 double y = -2.0; │ 299 double z = 2.0; │ 300 for (int i=0; i<20; i++) { │ 301 root.addChild( createSphere(0.5f, x, y, z, app) ); ├(1) 302 x += 0.3 * (double)i; │ 303 y += 0.3 * (double)i; │ 304 z -= (double)i; │ 305 } ┘ 306 307 lfog = new LinearFog();..................................(2) 308 lfog.setCapability(Fog.ALLOW_COLOR_READ); 309 lfog.setCapability(Fog.ALLOW_COLOR_WRITE); 310 lfog.setCapability(LinearFog.ALLOW_DISTANCE_WRITE); 311 lfog.setInfluencingBounds(bounds);.......................(3) 312 313 efog = new ExponentialFog();.............................(4) 314 efog.setCapability(Fog.ALLOW_COLOR_READ); 315 efog.setCapability(Fog.ALLOW_COLOR_WRITE); 316 efog.setCapability(ExponentialFog.ALLOW_DENSITY_WRITE); 317 efog.setInfluencingBounds(bounds);.......................(5) 318 319 fog = efog;..............................................(6) 320 root.addChild(fog); 321 322 return root;
サンプルでは 20個の球体を生成してシーンに addChild()
しています(1)。
(2)で LinearFog
を生成し、(3)で霧が適用される領域を設定しています。
(3)で ExponentialFog
を生成し、(4)で霧が適用される領域を設定しています。
(5)で Fog
型の変数 fog
を BranchGraph
に addChild()
していますが、(6)で fog
に ExponentialFog
を代入しているので、初期状態では ExponentialFog
が使われます。
実行中に Checkbox
で LinearFog
を選ぶと LinearFog
が使用されるようにしています。
霧の色を変えてみるとわかるのですが、霧の色と背景の色が同じでないと、霧による描画の効果がはっきりわかりません。
背景の色を変えるにはどうしたら良いでしょうか?
Java 3D には背景の色を変えたり、背景に画像を適用したり、背景に別のシーングラフを描画したりするための javax.media.j3d.Background
クラスが用意されています。
javax.media.j3d.Background
)javax.media.j3d.Background
は、背景の色、背景画像、背景用のシーングラフを描画します。
クラス継承 java.lang.Object | +--javax.media.j3d.SceneGraphObject | +--javax.media.j3d.Node | +--javax.media.j3d.Leaf | +--javax.media.j3d.Background クラス宣言 public class Background extends Leaf コンストラクター public Background() public Background(Color3f color) // 背景の色 public Background(float r, // 背景の色の赤色値 float g, // 背景の色の緑色値 float b) // 背景の色の青色値 public Background(ImageComponent2D image) // 背景に使用する画像 public Background(BranchGroup branch) // 背景に使用するシーングラフ メソッド (一部) public final void setApplicationBounds(Bounds region) // 背景が有効になる領域 public final void setApplicationBoundingLeaf(BoundingLeaf region) // 背景が有効になる領域リーフ
Background
のコンストラクターでは、背景の色、背景に使用する画像、または背景として描画するシーングラフを指定します。
ここで背景とは、無限遠にあるものを言います。実際には背景は常にいちばん後ろに描画されます。
背景に画像を適用する場合、ImageComponent2D
についてはテクスチャーマッピングのところで詳しく説明します。
Background
の使用方法は次の通りです。
ImageComponent2D
や、背景に使用するシーングラフを構築した BranchGroup
を用意するBackground
を生成する。必要なら色を指定するsetInfluencingBounds(), setInfluencingBoundingLeaf()
メソッドで適用される領域、または領域リーフを設定する。Background
をシーングラフに addChild()
するサンプルの実行結果を見てください。
この実行例では霧の色と背景の色を同じにしてみました。Fog
の効果が良く分かると思います。
サンプルのソースは次の通りです。
FogTest.java (一部)370 private BranchGroup createSceneGraph() { 371 BranchGroup root = new BranchGroup(); 372 root.setCapability(BranchGroup.ALLOW_DETACH); 373 374 BoundingSphere bounds = new BoundingSphere(new Point3d(), 10000.0); 375 376 background = createBackground(bounds);...(1) 377 root.addChild(background);...............(2) 378 379 root.addChild(createLight(0.3f, -0.3f, -0.3f)); 380 381 app = createAppearance(); 382 383 double x = -2.0; 384 double y = -2.0; 385 double z = 2.0; 386 for (int i=0; i<20; i++) { 387 root.addChild( createSphere(0.5f, x, y, z, app) ); 388 x += 0.3 * (double)i; 389 y += 0.3 * (double)i; 390 z -= (double)i; 391 } 392 393 lfog = new LinearFog(); 394 lfog.setCapability(Fog.ALLOW_COLOR_READ); 395 lfog.setCapability(Fog.ALLOW_COLOR_WRITE); 396 lfog.setCapability(LinearFog.ALLOW_DISTANCE_WRITE); 397 lfog.setInfluencingBounds(bounds); 398 399 efog = new ExponentialFog(); 400 efog.setCapability(Fog.ALLOW_COLOR_READ); 401 efog.setCapability(Fog.ALLOW_COLOR_WRITE); 402 efog.setCapability(ExponentialFog.ALLOW_DENSITY_WRITE); 403 efog.setInfluencingBounds(bounds); 404 405 fog = efog; 406 root.addChild(fog); 407 408 return root; 409 } 410 411 private Background createBackground(Bounds bounds) { 412 // Texture の生成 413 Image image = null;................................(3) 414 if (this.isStandalone) { ..........................(4) 415 // アプリケーションとして実行されている 416 Toolkit toolkit = Toolkit.getDefaultToolkit(); ┬(5) 417 image = toolkit.getImage("bg.jpg"); ┘ 418 } else { 419 // アプレットとして実行されている 420 image = getImage(getCodeBase(), "bg.jpg"); 421 } 422 MediaTracker mt = new MediaTracker(this); ┐ 423 mt.addImage(image, 0); ├(6) 424 mt.checkAll(true); │ 425 try { mt.waitForID(0); } catch (InterruptedException e) { e.printStackTrace(); } ┘ 426 this.loader = new TextureLoader(image, this);.............(7) 427 Background bg = new Background(this.loader.getImage());...(8) 428 bg.setCapability(Background.ALLOW_COLOR_READ); ┐ 429 bg.setCapability(Background.ALLOW_COLOR_WRITE); ├(9) 430 bg.setCapability(Background.ALLOW_IMAGE_WRITE); ┘ 431 bg.setApplicationBounds(bounds);..................(10) 432 return bg; 433 }
(1)で private
メソッドである createBackground()
を実行して Background
を生成し、(2)で BranchGroup
に addChild()
しています。
createBackground()
メソッドでは、(3)で画像のための java.awt.Image
型の変数を宣言しています。
(4)の if
文で isStandalone
フラグをみてアプリケーションとして実行されているのか、アプレットとして実行されているのかを判定しています。
アプリケーションとして実行されているときは、java.awt.Toolkit
クラスの static
メソッドである getDefaultToolkit()
で現在の Toolkit
オブジェクトを取得して、Toolkit#getImage()
でファイル名を指定して画像をファイルから読み込んでいます(5)。
アプレットとして実行されているときは Applet#getImage()
メソッドを使って、getCodeBase()
で得た codeBase
の URL にあるファイル名 face.gif
から画像を読み込んでいます。
(6)は java.awt.MediaTracker
を使った画像読み込みの同期処理です。waitForID()
で完全に画像が読み込まれるまで待ち続けています。
(7)で画像を読み込んだ Image
、このアプレット自身 this
(imageUpdate()
の通知先となる)をコンストラクター引数に指定して TxuterLoader
を生成しています。TextureLoader
は、java.awt.Image, java.net.URL,
または画像ファイルのファイル名を指定して Texture
や ImageComponent2D
を生成できる便利なクラスです。TextureLoader
についてはテクスチャーマッピングのところで詳しく説明します。
(8)でTextureLoader#getImage()
メソッドで ImageComponent2D
オブジェクトを取得し、取得した ImageComponent2D
を引数にして Background
を生成しています。
(9)で必要な capability bit
を設定し、(10)で setInfluencingBounds()
メソッドで背景が適用される対象となる領域を設定しています。
com.sun.j3d.utils.geometry.Text2D, javax.media.j3d.Text3D
を使うと、3次元空間に文字を描画することができます。
Text2D
では文字を画像として描画し、Text3D
では文字を3次元モデルとして描画することができます。
com.sun.j3d.utils.geometry.Text2D
com.sun.j3d.utils.geometry.Text2D
は文字を画像として描画します。
Text2D
は標準の javax.media.j3d
パッケージではなく、Sun のユーティリティーパッケージ com.sun.j3d.utils
パッケージのクラスとして提供されています。
クラス継承 java.lang.Object | +--javax.media.j3d.SceneGraphObject | +--javax.media.j3d.Node | +--javax.media.j3d.Leaf | +--javax.media.j3d.Shape3D | +--com.sun.j3d.utils.geometry.Text2D クラス宣言 public class Text2D extends Shape3D コンストラクター public Text2D(java.lang.String text, // 描画する文字列 Color3f color, // 色 java.lang.String fontName, // フォント名 int fontSize, // フォントサイズ (単位: ポイント) int fontStyle) // フォントスタイル
Text2D
のコンストラクターでは、描画する文字列、文字の色、フォント名、フォントサイズ (単位: ポイント)、フォントスタイルを指定できます。
フォントスタイルは java.awt.Font
クラスの定数フィールドを使って指定します。
java.awt.Font の定数フィールド public static final int PLAIN // 標準体 public static final int BOLD // 太文字 public static final int ITALIC // 斜体
サンプルの実行結果は次のようになります。
文字列を入力し、フォントを選び、色、サイズ、座標を入力して [Add]
ボタンを押すと Text2D
が追加されます。
いくつも追加してみると分かるのですが、先に追加した Text2D
は、後に追加した Text2D
よりも手前に表示されます。Text2D
の表示される順序は、Z座標ではなくシーングラフに追加された順番に影響されるようです。
サンプルのソースは次の通りです。
Text2DTest.java (一部)1 // Java 3D Test Applet 2 // Text2DTest.java 3 // Copyright (c) 1999 ENDO Yasuyuki 4 // mailto:yasuyuki@javaopen.org 5 // http://www.javaopen.org/j3dbook/index.html 6 7 import java.applet.*; 8 import java.awt.*; 9 import java.awt.event.*; 10 import javax.media.j3d.*; 11 import javax.vecmath.*; 12 import com.sun.j3d.utils.applet.MainFrame; 13 import com.sun.j3d.utils.universe.SimpleUniverse; 14 import com.sun.j3d.utils.geometry.Text2D; 15 import com.sun.j3d.utils.image.TextureLoader; 16 17 public class Text2DTest extends Applet { 18 boolean isStandalone = false; 19 20 private Canvas3D canvas = null; 21 private SimpleUniverse universe = null; 22 private BranchGroup scene = null; 23 private Background background = null; 24 private TextureLoader loader = null; 25 26 private Color3f bgcolor = new Color3f(0.0f, 0.0f, 0.0f); // 背景色 黒 27 private Color3f fontcolor = new Color3f(1.0f, 0.0f, 0.0f); // フォント色 赤 28 private Vector3f fontposition = new Vector3f(0.0f, 0.0f, 0.0f); // フォント位置 29 30 private TuplePanel fcpanel = new TuplePanel(fontcolor); 31 private TuplePanel fppanel = new TuplePanel(fontposition); 32 33 public Text2DTest() { 34 this(false); 35 } 36 37 public Text2DTest(boolean isStandalone) { 38 this.isStandalone = isStandalone; 39 this.setLayout(new BorderLayout()); : : 133 Transform3D t3d = new Transform3D(); 134 t3d.setTranslation( new Vector3f(fppanel.getTuple3f()) ); 135 TransformGroup trans = new TransformGroup(t3d); 136 trans.addChild( 137 new Text2D( txfield.getText(), ┐ 138 new Color3f(fcpanel.getTuple3f()), ├(1) 139 fchoice.getSelectedItem(), │ 140 size, style ) ); ┘ 141 universe.getLocale().removeBranchGraph(scene);.....(2) 142 scene.addChild(trans);.............................(3) 143 universe.addBranchGraph(scene);....................(4) 144 } 145 }); 146 dpanels[2].add(addbutton); 147 148 } 149 150 public void init() {} 151 152 public void start() { 153 GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration(); 154 this.canvas = new Canvas3D(config); 155 this.add(this.canvas, BorderLayout.CENTER); 156 157 universe = new SimpleUniverse(canvas); 158 universe.getViewingPlatform().setNominalViewingTransform(); 159 160 scene = createSceneGraph(); 161 universe.addBranchGraph(scene); 162 } 163 164 private BranchGroup createSceneGraph() { 165 BranchGroup root = new BranchGroup(); 166 root.setCapability(BranchGroup.ALLOW_DETACH); 167 168 BoundingSphere bounds = new BoundingSphere(new Point3d(), 10000.0); 169 170 background = createBackground(bounds); 171 root.addChild(background); 172 173 return root; 174 }
[Add]
ボタンが押されるたびに、Text2D
を BranchGroup
に addChild()
するようにしました。
このサンプルでは次のような方法で使用可能なフォントの一覧を取得しています。
83 Font[] fonts 84 = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
(1)で入力値を元に Text2D
を生成しています。
実行時に Text2D
を追加するために、(2)でLocale
からBranchGroup
をremoveBranchGraph()
しています。
(3)でText2D
が追加されている
TransformGroup
をBranchGroup
にaddChild()
しています。
Text2D
を追加したら、(4)でふたたびSimpleUniverse
に BranchGroup
をaddBranchGraph()
しています。
javax.media.j3d.Text3D
javax.media.j3d.Text3D
は文字フォントから座標データを取得して、厚みのある3次元モデルとして抽出します。
クラス継承 java.lang.Object | +--javax.media.j3d.SceneGraphObject | +--javax.media.j3d.NodeComponent | +--javax.media.j3d.Geometry | +--javax.media.j3d.Text3D クラス宣言 public class Text3D extends Geometry コンストラクター public Text3D() public Text3D(Font3D font3D) // 元になる Font3D オブジェクト public Text3D(Font3D font3D, // 元になる Font3D オブジェクト java.lang.String string) // 描画する文字列 public Text3D(Font3D font3D, // 元になる Font3D オブジェクト java.lang.String string, // 描画する文字列 Point3f position) // 文字列の描画位置 public Text3D(Font3D font3D, // 元になる Font3D オブジェクト java.lang.String string, // 描画する文字列 Point3f position, // 文字列の描画位置 int alignment, // 文字列の整列方法 int path) // 文字列の描画方向 定数フィールド (一部) public static final int ALIGN_CENTER // 中央揃え public static final int ALIGN_FIRST // 先頭揃え public static final int ALIGN_LAST // 末尾揃え public static final int PATH_LEFT // 左向きに描画 public static final int PATH_RIGHT // 右向きに描画 public static final int PATH_UP // 上向きに描画 public static final int PATH_DOWN // 下向きに描画
Text3D
のコンストラクターでは javax.media.j3d.Font3D
を使って文字フォントを設定します。また、描画する文字列、文字の位置座標、整列方法、描画方向を指定することもできます。
Text2D
は Shape3D
のサブクラスだったので直接 BranchGroup
に addChild()
しましたが、Text3D
は javax.media.j3d.Geometry
のサブクラスなので、Shape3D
などに設定して使用します。
javax.media.j3d.Font3D
javax.media.j3d.Font3D
は java.awt.Font
元に3次元フォントデータを構築します。
クラス継承 java.lang.Object | +--javax.media.j3d.Font3D クラス宣言 public class Font3D extends java.lang.Object コンストラクター public Font3D(java.awt.Font font, // 元になる java.awt.Font オブジェクト FontExtrusion extrudePath) // 側面の押し出し public Font3D(java.awt.Font font, // 元になる java.awt.Font オブジェクト double tessellationTolerance, // テセレーションの精度 FontExtrusion extrudePath) // 側面の押し出し
Font3D
のコンストラクターでは java.awt.Font
と javax.media.j3d.FontExtrusion
を指定します。
FontExtrusion
はフォントの側面の形状を決定するためのクラスで、引数無しのコンストラクターで FontExtrusion
を生成すると 0.2
の直線が使われます。
サンプルの実行結果は次のようになります。
Text3D
で描画する文字や、フォント、色、位置、整列方法、描画方向を変えることができます。
サンプルのソースは次の通りです。
1 // Java 3D Test Applet 2 // Text3DTest.java 3 // Copyright (c) 1999 ENDO Yasuyuki 4 // mailto:yasuyuki@javaopen.org 5 // http://www.javaopen.org/j3dbook/index.html 6 7 import java.applet.*; 8 import java.awt.*; 9 import java.awt.event.*; 10 import javax.media.j3d.*; 11 import javax.vecmath.*; 12 import com.sun.j3d.utils.applet.MainFrame; 13 import com.sun.j3d.utils.universe.SimpleUniverse; 14 15 public class Text3DTest extends Applet { 16 private Canvas3D canvas = null; 17 private SimpleUniverse universe = null; 18 private BranchGroup scene = null; 19 private TransformGroup trans = null; 20 private TransformGroup strans = null; 21 private float scale = 0.2f; 22 private Material mat = null; 23 private Font[] fonts = null; 24 private int nfont = 0; 25 private Font font = null; 26 private int style = java.awt.Font.PLAIN; 27 private String text = "Java 3D"; 28 private Text3D text3d = null; 29 30 private Color3f fontcolor = new Color3f(1.0f, 0.0f, 0.0f); // フォント色 赤 31 private TuplePanel fcpanel = new TuplePanel(fontcolor); 32 33 private Point3f fontposition = new Point3f(0.0f, 0.0f, 0.0f); // フォント位置 原点 34 private TuplePanel fppanel = new TuplePanel(fontposition); 35 : : 199 private BranchGroup createSceneGraph() { 200 BranchGroup root = new BranchGroup(); 201 root.setCapability(BranchGroup.ALLOW_DETACH); 202 203 BoundingSphere bounds = new BoundingSphere(new Point3d(), 10000.0); 204 205 root.addChild( createLight(0.3f, -0.3f, -0.3f, bounds) ); 206 207 AmbientLight alight = new AmbientLight(); 208 alight.setInfluencingBounds(bounds); 209 root.addChild(alight); 210 211 trans = new TransformGroup(); 212 trans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); 213 trans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); 214 root.addChild(trans); 215 216 Transform3D t3d = new Transform3D(); 217 t3d.setScale(scale); 218 219 strans = new TransformGroup(t3d); 220 strans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); 221 strans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); 222 trans.addChild(strans); 223 224 text3d = ┐ 225 new Text3D( new Font3D( font, new FontExtrusion() ),...(2) │ 226 text, ├(1) 227 fontposition_, │ 228 Text3D.ALIGN_FIRST, │ 229 Text3D.PATH_RIGHT ); ┘ 230 text3d.setCapability(Text3D.ALLOW_FONT3D_WRITE); ┐ 231 text3d.setCapability(Text3D.ALLOW_STRING_WRITE); │ 232 text3d.setCapability(Text3D.ALLOW_ALIGNMENT_READ); ├(3) 233 text3d.setCapability(Text3D.ALLOW_ALIGNMENT_WRITE); │ 234 text3d.setCapability(Text3D.ALLOW_PATH_READ); │ 235 text3d.setCapability(Text3D.ALLOW_PATH_WRITE); ┘ 236 237 Shape3D shape = new Shape3D(text3d, createAppearance());...(4) 238 239 strans.addChild(shape);....................................(5) 240 241 return root; 242 }
(1)で Text3D
を構築しています。
(2)で Font3D
を生成しています。フォントは Choice
で選ばれたフォントが使用され、FontExtrusion
は引数無しのコンストラクターで生成しているので側面には長さ 0.2
の直線が使用されます。
文字位置は原点 (0.0, 0.0, 0.0)
、整列方法は先頭揃え、描画方向は右方向です。
(3)で capability bit
をセットし、(4)で Text3D
をコンストラクター引数にして Shape3D
を構築し、(5)で TransformGorup
に addChild()
しています。
これを書いている時点では Java 3D バージョン 1.1.x, 1.2 Alpha1, 1.2 Beta1 の Text3D
にバグがあり、漢字かな混じりの日本語文字列では正常に3次元モデルが構築できないようです。
javax.media.j3d.FontExtrusion
javax.media.j3d.FontExtrusion
は Font3D
で構築する3次元フォントの側面の形状を定義するクラスです。
クラス継承 java.lang.Object | +--javax.media.j3d.FontExtrusion クラス宣言 public class FontExtrusion extends java.lang.Object コンストラクター public FontExtrusion() public FontExtrusion(java.awt.Shape extrusionShape) // 側面に適用する java.awt.Shape public FontExtrusion(java.awt.Shape extrusionShape, // 側面に適用する java.awt.Shape double tessellationTolerance) // テセレーションの精度
java.awt.Shape
で側面の形状を設定することができます。デフォルトでは長さ 0.2
の直線です。
サンプルの実行結果を見てください。
Checkbox
で java.awt.Line2D, java.awt.CubicCurbe2D, java.awt.QuadCurve2D
を選び、線の形状を変更できます。
サンプルのソースは次の通りです。
Text3DTest.java (一部)1 // Java 3D Test Applet 2 // Text3DTest.java 3 // Copyright (c) 1999 ENDO Yasuyuki 4 // mailto:yasuyuki@javaopen.org 5 // http://www.javaopen.org/j3dbook/index.html 6 7 import java.applet.*; 8 import java.awt.*; 9 import java.awt.geom.*; 10 import java.awt.event.*; 11 import javax.media.j3d.*; 12 import javax.vecmath.*; 13 import com.sun.j3d.utils.applet.MainFrame; 14 import com.sun.j3d.utils.universe.SimpleUniverse; 15 16 public class Text3DTest extends Applet { 17 private Canvas3D canvas = null; 18 private SimpleUniverse universe = null; 19 private BranchGroup scene = null; 20 private TransformGroup trans = null; 21 private TransformGroup strans = null; 22 private float scale = 0.2f; 23 private Material mat = null; 24 private Font[] fonts = null; 25 private int nfont = 0; 26 private Font font = null; 27 private int style = java.awt.Font.PLAIN; 28 private String text = "Java 3D"; 29 private Text3D text3d = null; 30 private Shape shape = null; 31 32 private Line2D.Double line2d = new Line2D.Double(0.0, 0.0, 1.0, 0.0); 33 34 private CubicCurve2D.Double c2d = 35 new CubicCurve2D.Double(0.0, 0.0, 0.2, 0.7, 0.7, 0.8, 1.0, 1.0); 36 37 private QuadCurve2D.Double q2d = 38 new QuadCurve2D.Double(0.0, 0.0, 0.0, 1.0, 1.0, 1.0); 39 40 private Color3f fontcolor = new Color3f(1.0f, 0.0f, 0.0f); // フォント色 赤 41 private TuplePanel fcpanel = new TuplePanel(fontcolor_); 42 43 private Point3f fontposition = new Point3f(0.0f, 0.0f, 0.0f); // フォント位置 原点 44 private TuplePanel fppanel = new TuplePanel(fontposition_); 45 46 public Text3DTest() { 47 this.setLayout(new BorderLayout()); 48 49 shape = line2d; : : 757 private BranchGroup createSceneGraph() { 758 BranchGroup root = new BranchGroup(); 759 root.setCapability(BranchGroup.ALLOW_DETACH); 760 761 BoundingSphere bounds = new BoundingSphere(new Point3d(), 10000.0); 762 763 root.addChild( createLight(0.3f, -0.3f, -0.3f, bounds) ); 764 765 AmbientLight alight = new AmbientLight(); 766 alight.setInfluencingBounds(bounds); 767 root.addChild(alight); 768 769 trans = new TransformGroup(); 770 trans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); 771 trans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); 772 root.addChild(trans); 773 774 Transform3D t3d = new Transform3D(); 775 t3d.setScale(scale); 776 777 strans = new TransformGroup(t3d); 778 strans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); 779 strans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); 780 trans.addChild(strans); 781 782 text3d = 783 new Text3D( new Font3D( font, new FontExtrusion(shape) ), 784 text, 785 fontposition_, 786 Text3D.ALIGN_FIRST, 787 Text3D.PATH_RIGHT ); 788 text3d.setCapability(Text3D.ALLOW_FONT3D_WRITE); 789 text3d.setCapability(Text3D.ALLOW_STRING_WRITE); 790 text3d.setCapability(Text3D.ALLOW_ALIGNMENT_READ); 791 text3d.setCapability(Text3D.ALLOW_ALIGNMENT_WRITE); 792 text3d.setCapability(Text3D.ALLOW_PATH_READ); 793 text3d.setCapability(Text3D.ALLOW_PATH_WRITE); 794 795 Shape3D shape3d = new Shape3D(text3d, createAppearance()); 796 797 strans.addChild(shape3d); 798 799 return root; 800 }
クラスの最初で java.awt.Line2D.Double, java.awt.CubicCurbe2D.Double, java.awt.QuadCurve2D.Double
を宣言し、コンストラクターで初期値として Line2D.Double
を使用するようにしています。
Font3D
の生成時にコンストラクターで代入した java.awt.Line2D.Double
が使用されます。実行時に Checkbox
で他の Shape
に切替えられます。
jamax.media.j3d.Morph
javax.media.j3d.Morph
クラスは、物体の形状をなめらかに変形させるためのクラスです。
クラス継承 java.lang.Object | +--javax.media.j3d.SceneGraphObject | +--javax.media.j3d.Node | +--javax.media.j3d.Leaf | +--javax.media.j3d.Morph クラス宣言 public class Morph extends Leaf コンストラクター public Morph(GeometryArray[] geometryArrays) // 変化ごとの GeometryArray の配列 public Morph(GeometryArray[] geometryArrays, // 変化ごとの GeometryArray の配列 Appearance appearance) // 概観上の属性 メソッド (一部) public final void setWeights(double[] weights) // 変化ごとの混合比
Morph
のコンストラクターは Shape3D
に良く似ています。だたし Morph
には GeometryArray
の配列を引数にとるコンストラクターしかありません。
Morph
には次のような制約があります。
GeometryArray
配列の各要素は、すべて同じ頂点数であることGeometryArray
配列の各要素は、すべて同じクラスであることsetWeights()
メソッドで指定する float
配列の要素数は、GeometryArray
配列の要素数と同じであることsetWeights()
メソッドで指定する float
配列のすべての要素を合計した値は、必ず 1.0
であることこの制約からわかるように、Morph
は各頂点を一次補間して変形を実現しているだけです。float
配列 weights
で指定された混合比に従って頂点座標が補間計算されます。
例えば GeometryArray
配列 geometryArrays
の要素数が 2
だったとします。
weights
配列の各要素が [ 1.0f, 0.0f ]
なら、計算結果の頂点座標は geometryArrays[0]
の頂点座標と同じになります。weights
が [ 0.0f, 1.0f ]
なら、計算結果の頂点座標は geometryArrays[1]
の頂点座標と同じになります。
weights
が [ 0.5f, 0.5f ]
なら、geometryArrays[0]
の頂点座標と geometryArrays[1]
の頂点座標を1:1の比率で補間した頂点座標が計算されます。
サンプルの実行結果を見てください。
このサンプルでは外部ファイルから "J", "3", "D" の形の物体を読み込み、スクロールバーの位置に応じて物体を変形させています。スクロールバーがいちばん左にあるとき "J"、中央が "3"、いちばん右で "D" の形に変形させています。
ここではスクロールバーで weights
配列の各要素の値を変化させていますが、後に解説する javax.media.j3d.Interpolator
を使って変形をアニメーションをアニメーションすることも可能です。その場合は Interpolator
を継承したクラスを自分で書くことになります。Java 3D に付属のデモサンプル Morphing
に MorphingBehavior.java
というソースがあるので参考にしてください。
サンプルのソースは次の通りです。
MorphTest.java (一部)76 private BranchGroup createSceneGraph() { 77 BranchGroup root = new BranchGroup(); 78 79 Bounds bounds = new BoundingSphere(new Point3d(), 100.0); 80 81 Light alight = new AmbientLight(); 82 alight.setInfluencingBounds(bounds); 83 root.addChild(alight); 84 85 Light light = new DirectionalLight( new Color3f(1.0f, 1.0f, 1.0f), 86 new Vector3f(-0.57f, -0.57f, -0.57f) ); 87 light.setInfluencingBounds(bounds); 88 root.addChild(light); 89 90 Transform3D t3d = new Transform3D(); 91 t3d.setScale(0.4); 92 TransformGroup trans = new TransformGroup(t3d); 93 trans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); 94 trans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); 95 trans.setCapability(TransformGroup.ENABLE_PICK_REPORTING); 96 root.addChild(trans); 97 98 MouseRotate rotate = new MouseRotate(trans); 99 rotate.setSchedulingBounds(bounds); 100 root.addChild(rotate); 101 102 MouseTranslate translate = new MouseTranslate(trans); 103 translate.setSchedulingBounds(bounds); 104 root.addChild(translate); 105 106 MouseZoom zoom = new MouseZoom(trans); 107 zoom.setSchedulingBounds(bounds); 108 root.addChild(zoom); 109 110 GeometryArray[] garray = new GeometryArray[3];...(1) 111 112 try { 113 garray[0] = loadObjFile("j.obj"); ┐ 114 garray[1] = loadObjFile("3.obj"); ├(2) 115 garray[2] = loadObjFile("d.obj"); ┘ 116 } catch (Exception e) { 117 e.printStackTrace();//DEBUG 118 } 119 120 Appearance app = new Appearance(); 121 Material mat = new Material(); 122 mat.setDiffuseColor(new Color3f(0.0f, 0.0f, 1.0f)); 123 app.setMaterial(mat); 124 125 morph = new Morph(garray, app); ┐ 126 morph.setCapability(Morph.ALLOW_WEIGHTS_WRITE); ├(3) 127 morph.setWeights(weights); │ 128 trans.addChild(morph); ┘ 129 130 return root; 131 }
(1)で要素数 3の GeometryArray
配列を宣言しています。
(2)で外部ファイルから物体を読み込んでいます。
(3)でMorph
生成、weights
配列設定、TransformGroup
への addChild()
を行っています。
外部ファイルには Wavefront .obj
形式のものを3つ用意しました。
com.sun.j3d.loaders.Loader
インターフェース-Morph
のサンプルでは、同じ頂点配列を持つ GeometryArray
を外部ファイルから読み込んでいます。
まずあらかじめモデリングした物体を変形させ、同じ頂点座標を持つファイルとして別名保存しました。
つぎに com.sun.j3d.loaders.Loader
インターフェースの実装クラスを使って外部ファイルから物体を読み込みます。
interface 宣言 public interface Loader メソッド (一部) public Scene load(java.lang.String fileName) // 読み込み元ファイル名 public Scene load(java.net.URL url) // 読み込み元 URL public Scene load(java.io.Reader reader) // 読み込み元 Reader 定数フィールド public static final int LOAD_LIGHT_NODES // Light ノードを読み込む public static final int LOAD_FOG_NODES // Fog ノードを読み込む public static final int LOAD_BACKGROUND_NODES // Background ノードを読み込む public static final int LOAD_BEHAVIOR_NODES // Behavior ノードを読み込む public static final int LOAD_VIEW_GROUPS // Group ノードを読み込む public static final int LOAD_SOUND_NODES // Sound ノードを読み込む public static final int LOAD_ALL // すべてを読み込む
読み込まれた結果は com.sun.j3d.leaders.Scene
インターフェースとして取得できます。
Scene
には様々な種類のオブジェクトを取得するメソッドがあります。
interface 宣言 public interface Scene メソッド (一部) public BranchGroup getSceneGroup() // 読み込んだすべての root の BranchGroup を取得
Java 3Dには、Loader
interface を実装したクラスとして次のものが付属しています。
com.sun.j3d.loaders.objectfile.ObjectFile // Wavefront の .obj ファイルを読み込む com.sun.j3d.loaders.lw3d.Lw3dLoader // LightWave のファイルを読み込む
今回のサンプルでは ObjectFile
を使用しました。
MorphTest.java (一部)18 import com.sun.j3d.loaders.objectfile.ObjectFile; : 23 public class MorphTest extends Applet { : 27 ObjectFile loader = null; : 133 private GeometryArray loadObjFile(String filename) 134 throws FileNotFoundException, MalformedURLException, 135 IncorrectFormatException, ParsingErrorException 136 { 137 Scene scene = null; 138 139 if (loader == null) loader = new ObjectFile(); 140 if (isStandalone) { 141 scene = loader.load(filename); 142 } else { 143 URL url = new URL(getCodeBase(), filename); 144 scene = loader.load(url); 145 } 146 BranchGroup bg = scene.getSceneGroup(); 147 Shape3D shape = (Shape3D)bg.getChild(0); 148 return (GeometryArray)shape.getGeometry(); 149 }
アプリケーションとして動作しているときはファイル名指定で、アプレットとして動作しているときは java.net.URL
を指定して .obj
ファイルを読み込んでいます。
読み込まれた Scene
から BranchGroup
を取得し、そこからさらに Shape3D
を取得し、Shape3D#getGoemetry()
で Goemetry
オブジェクトを取得し、GeometryArray
型にキャストして戻り値として返しています。