今回はJavaFX上の3Dオブジェクトである3Dシェイプとメッシュを見ていく。マテリアル(色・テクスチャ)の設定については、次回の記事にて扱う。
■ 3Dオブジェクトのクラス一覧
JavaFXで利用可能な3Dオブジェクト・クラスの一覧は以下の通りである。注意点としてメッシュ以外の位置指定は、すべて重心位置で設定することである。
3Dオブジェクト |
クラス |
内容 |
立方体、直方体 |
Box |
縦・横・奥行を持つ直方体を出力する |
球 |
Sphere |
半径を指定して球を出力する |
円柱 |
Cylinder |
半径と高さを指定して、球を出力する |
メッシュ |
MeshView |
複数の頂点を面でつなげた立体を出力する。3Dモデルの出力などで利用する。 |
■ メッシュの構築
JavaFXでのメッシュ構築は以下の手順で実行する。
①頂点の定義
必要な頂点を(x,y,z)座標で指定して作成する。作成した頂点には番号を付与し、
後で参照できるようにする。
②面の作成
①で作成した頂点の番号を3つ指定して、1つの面(三角形)を作成する。
頂点の指定順序で面に裏表を設定する。裏表の設定方法は以下の通り。
例えば、面を( v1 , v2 , v3 )の3つの頂点で作成することを考える。
3つの頂点をカメラ位置から見た場合、半時計周りの順番で定義されている側が表となり、
逆に、時計回りの順番で定義されている側が裏となる。
■ カリングの設定 / setCullFace関数
カリング処理(Culling)とは、3Dオブジェクトの面の片面(裏か表)を描画しない仕組みである。3Dレンダリングで物体を描画する際に面の裏・表は別々に描画される。しかし、球のように面の裏側が見えない物体(球の表面はすべて表面)の場合、裏面を描写しなくても問題がない場合が多い。裏面を描画しないことで描画処理量が単純に1/2になるため、ようにすることで、見た目は変わらずに描画処理を軽くすることができる。もちろん、平面のような裏・表両面を描画する必要がある3Dオブジェクトではカリングしないよう設定する必要がある。
定数 |
内容 |
CullFace.NONE |
裏・表の両方を描画する |
CullFace.BACK |
表面を描画し、裏面を描画しない。デフォルト値 |
CullFace.FRONT |
表面を描画せず、裏面を描画する |
■ 描画モデルの設定 / setDrawMode関数
描画モデルでは、3Dオブジェクトを描画する際に面を表示するか、枠線のみ表示するかを設定できる。
定数 |
内容 |
DrawMode.FILL |
面を塗りつぶす。デフォルト値 |
DrawMode.LINE |
面を塗りつぶさず、輪郭線のみ描画する |
■ サンプルプログラム
以下に3Dオブジェクトの表示と、カリング・描画モデルの設定方法を示すサンプルコードを示す。サンプルでは6つの3Dオブジェクトを作成しているが、カリングにより1つの3Dオブジェクトが描画されていない。
◇サンプルコード
package application_fx;
import javafx.application.Application;
import javafx.geometry.Point3D;
import javafx.scene.Camera;
import javafx.scene.Group;
import javafx.scene.LightBase;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Box;
import javafx.scene.shape.CullFace;
import javafx.scene.shape.Cylinder;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.Sphere;
import javafx.scene.shape.TriangleMesh;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class TestShape3D extends Application
{
public static void main(String[] args)
{ launch( args );}
@Override
public void start(Stage primaryStage) throws Exception
{
// ルートノードの作成
Group root = create3DGroup();
// カメラを設定
Camera camera = new PerspectiveCamera( true );
camera.getTransforms().add( new Rotate( 30 , 0 , 0 , 0 , new Point3D( -1 , -1 , 0 ) ) );
camera.getTransforms().add( new Translate( 0.0 , 0.0 , -30 ) );
root.getChildren().add( camera );
// 照明を設定
LightBase light = new PointLight();
light.setTranslateX( 0.0 );
light.setTranslateY( -20.0 );
light.setTranslateZ( -30.0 );
root.getChildren().add( light );
// 3D用のーンを作成
Scene scene = new Scene( root , 500 , 500 , true );
scene.setFill( Color.BLACK );
scene.setCamera( camera );
// ウィンドウ表示
primaryStage.setScene( scene );
primaryStage.show();
}
/**
* 3Dオブジェクトのグループを作成する
* @return
*/
public Group create3DGroup()
{
// シーングラフのルートを作成
Group root = new Group();
// BOXを作成
Box box = new Box();
box.setWidth( 4 );
box.setHeight( 4 );
box.setDepth( 4 );
box.setTranslateX( -5 );
box.setTranslateY( -3 );
root.getChildren().add( box );
// 球の作成
Sphere sphere = new Sphere();
sphere.setRadius( 2 );
sphere.setTranslateX( 0 );
sphere.setTranslateY( -3 );
root.getChildren().add( sphere );
// 円柱の作成
Cylinder cylinder = new Cylinder();
cylinder.setRadius( 2 );
cylinder.setHeight( 4 );
cylinder.setTranslateX( 5 );
cylinder.setTranslateY( -3 );
root.getChildren().add( cylinder );
// メッシュの作成
// 【頂点座標】
// p0┏━┓p3
// ┃\┃
// p1┗━┛p2
TriangleMesh mesh = new TriangleMesh();
float[] points = { -2 ,-2 ,0 , // p0
-2 ,2 ,0 , // p1
2 ,2 ,0 , // p2
2 ,-2 ,0 }; // p3
float[] texCoords = { 0 , 0 };
int[] faces = { 0 , 0 , 1 , 0 , 2 , 0,
2 , 0 , 3 , 0 , 0 , 0 };
mesh.getPoints().addAll( points );
mesh.getTexCoords().addAll( texCoords );
mesh.getFaces().addAll( faces );
// メッシュビュー1(左)を作成
MeshView meshView1 = new MeshView();
meshView1.setMesh( mesh );
meshView1.setTranslateX( -5 );
meshView1.setTranslateY( 4 );
meshView1.setDrawMode( DrawMode.FILL );
root.getChildren().add( meshView1 );
// メッシュビュー2(中央)を作成
MeshView meshView2 = new MeshView();
meshView2.setMesh( mesh );
meshView2.setTranslateY( 4 );
meshView2.setDrawMode( DrawMode.LINE );
root.getChildren().add( meshView2 );
// メッシュビュー3(右)を作成
MeshView meshView3 = new MeshView();
meshView3.setMesh( mesh );
meshView3.setTranslateX( 5 );
meshView3.setTranslateY( 4 );
meshView3.setDrawMode( DrawMode.FILL );
meshView3.setCullFace( CullFace.FRONT );
root.getChildren().add( meshView3 );
return root;
}
}
◇実行結果
◇解説
- 直方体の作成時には、幅(x座標)・高さ(y座標)・奥行(z座標)を指定する(68行目~73行目)。3Dオブジェクトもシーングラフへ追加することで自動的に描画される(74行目)。ただし、2Dと3Dのオブジェクトは同じシーン内に表示できず、3Dシーンではシーン作成時にシーンバッファを有効にする必要がある(48行目)。
- 球と円柱も、直方体と同様に作成後にシーングラフに追加する(77行目~89行目)。直方体も球も円柱も、作成直後は物体の重心が座標(0,0,0)にくるように配置される。このため、setTranslate○○関数で物体位置を移動させている。
- メッシュの描画は『メッシュの作成』と『メッシュの出力』の2段階に分かれる。メッシュの作成では、頂点と面を指定してジオメトリデータを作成する(96行目~106行目)。頂点の宣言では、(x,y,z)座標を示す小数をfloat型の1次元配列に格納していく。ここでは、1つめの頂点座標は(-2,-2,0)を表し、2つめの頂点座標は(-2,2,0)を表す(97行目~100行目)。今回はテクスチャを用いないため、ダミーデータを設定している(101行目)。面の作成では、1つの面の作成に、3頂点の番号と3テクスチャの番号を指定している。ここでは、(頂点,テクスチャ)=(0,0) , (1,0) , (2,0)の3つの組で1つの面を構築している(102行目)。
- メッシュの出力では、作成したメッシュデータを表示するためのビューを作成・登録する(109行目~130行目)。画像の出力と同様、1つのメッシュの出力を別々のビューに設定可能である。
- 3Dオブジェクトの描画モード設定では、面の出力(上図の左下)と線の出力(上図の下中央)が指定できる(113行目、120行目)。
- カリング設定で表面の描画を省略するようにすると、出力されなくなる(129行目)。もちろん、カメラ位置を裏側に移動させると裏面は描画されている。
■ 参照
- JavaDoc - クラスShape3D
- JavaDoc - クラスTriangleMesh