忍者ブログ

軽Lab

 Javaを中心とした、プログラミング関係のナレッジベース

Home > > JavaFX ビルボード

JavaFX ビルボード

×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

Home > > JavaFX ビルボード

- ランダム記事 -

コメント

ただいまコメントを受けつけておりません。

Home > Java 応用・実験 > JavaFX ビルボード

JavaFX ビルボード

今回は、3Dゲームでよく使われるビルボードをJavaFXで実装する方法について見ていく。

■ ビルボードとは

 ビルボードを簡単に言うと、常にカメラ方向に向いている面である。ビルボードを利用することで3Dの世界に2Dの画像を埋め込むことができるため、3Dゲームのエフェクトや文字表示などに利用される。その他、背景には3Dを利用するが、キャラクタだけは2D画像を利用したいという場合にもビルボードが利用される。

 また、ビルボードを利用することでポリゴン数が削減できるため、3Dオブジェクトを2次元画像で代用して処理を軽量化する用途にも利用される。

 

■ サンプルプログラム

 以下にJavaFXでビルボードを実装するサンプルプログラムを示す。サンプルでは、xyz軸とキャラクター画像が描かれたビルボードを1つ表示している。画面をクリックするとカメラがx軸を中心とした回転を始める。しかし、ビルボードは常にカメラを向いているため、キャラクター画像が動いていないように見えることが確認できる。。

◇サンプルコード
package application_fx_functional;

import java.io.File;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Camera;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Mesh;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.TriangleMesh;
import javafx.scene.transform.Affine;
import javafx.scene.transform.NonInvertibleTransformException;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Transform;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;

public class TestBillBoard extends Application {


    public static void main(String[] args)
    {
        launch( args );
    }
    
    @Override
    public void start(Stage primaryStage) throws Exception
    {
        // シーングラフの構成
        Group       root        = new Group();
        
        // シーンの作成
        // 3Dシーンの奥行きを表現するため、Zバッファを有効にする
        Scene   scene       = new Scene( root , 600 , 480 , true );
        scene.setFill( Color.GRAY );
        
        // カメラ設定
        PerspectiveCamera   camera  = new PerspectiveCamera( true );
        camera.setFarClip( 3000 );
        camera.getTransforms().add( new Rotate( 60 , -camera.getTranslateX() , -camera.getTranslateY() , -camera.getTranslateZ() , Rotate.X_AXIS ) );
        camera.getTransforms().add( new Rotate( 30 , -camera.getTranslateX() , -camera.getTranslateY() , -camera.getTranslateZ() , Rotate.Y_AXIS ) );
        camera.getTransforms().add( new Translate( 0 , 0 , -10 ) );
        scene.setCamera( camera );
        
        // 中心軸を表示
        // x軸=赤、y軸=青、z軸=緑
        Color[]     axisColor   = { Color.RED , Color.BLUE , Color.GREEN };
        double[]    axisSize    = { 10.0 , 0.05 , 0.05 ,
                                    0.05 , 10.0 , 0.05 ,
                                    0.05 , 0.05 , 10.0  };
        for( int i = 0 ; i < 3 ; i++ )
        {
            // 軸船を作成
            Box     axis        = new Box();
            axis.setWidth(  axisSize[ i*3     ] );
            axis.setHeight( axisSize[ i*3 + 1 ] );
            axis.setDepth(  axisSize[ i*3 + 2 ] );
            
            // 色を設定
            PhongMaterial   material    = new PhongMaterial();
            material.setDiffuseColor( axisColor[i] );
            axis.setMaterial( material );
            
            root.getChildren().add( axis );
        }
        
        // ビルボードを作成
        BillBoard   billBoard   = new BillBoard( camera );
        Image       img         = new Image( new File( "img/chara_one.png" ).toURI().toString() );
        billBoard.setImage( img );
        root.getChildren().add( billBoard );
        
        // ウィンドウ表示
        primaryStage.setScene( scene );
        primaryStage.show();
        
        // カメラ移動のアニメーション・クラスを作成
        AnimationTimer  timer   = new AnimationTimer()
        {
            private long        startTime   = 0;
            private final long  CYCLE_TIME  = 5000;     // ミリ秒
            
            @Override
            public void handle(long t)
            {
                // 開始時間を取得 
                if( startTime == 0 ){ startTime = t; };
                
                // 回転角度を計算
                long    time        = startTime - t;                        // 経過時間
                long    cycleTime   = CYCLE_TIME * 1000000L;                // アニメーションのサイクル
                double  percent     = (double) ( time % cycleTime ) / cycleTime;     // アニメーションの進捗度合
                double  angle       = 360.0 * percent;
                
                // カメラ設定(x軸周りで回転)
                camera.getTransforms().clear();
                camera.getTransforms().add( new Rotate( angle , -camera.getTranslateX() , -camera.getTranslateY() , -camera.getTranslateZ() , Rotate.X_AXIS ) );
                camera.getTransforms().add( new Rotate( 30    , -camera.getTranslateX() , -camera.getTranslateY() , -camera.getTranslateZ() , Rotate.Y_AXIS ) );
                camera.getTransforms().add( new Translate( 0 , 0 , -10 ) );
            }
        };
        
        // 画面クリックでアニメーション開始
        primaryStage.addEventHandler( MouseEvent.MOUSE_CLICKED , e ->  timer.start() );
        
    }

    
    /**
     * ビルボードを表すクラス
     * @author tomo
     *
     */
    public class BillBoard extends MeshView
    {
        // ビルボードを関連付けるカメラ
        private Camera      camera;
        
        /**
         * カメラを指定してビルボードを作成
         * @param camera ビルボードを関連付けるカメラ
         * @throws NonInvertibleTransformException 
         */
        public BillBoard( Camera camera ) throws NonInvertibleTransformException
        {
            // カメラを設定付ける
            this.camera     = camera;
            
            // 面メッシュを作成
            setMesh( createTriangleMesh() );
            
            // 空のマテリアルを設定
            setMaterial( new PhongMaterial() );
            
            // カメラが未指定の場合、
            // 普通のメッシュとして表示するため、続く処理を行わない
            if( camera == null ){ return; } 
            
            // 面メッシュをカメラに対して垂直に設定(初期設定)
            Transform   cameraTrans     = this.camera.getLocalToSceneTransform();
            Affine      billTrans       = new Affine( cameraTrans.getMxx() , cameraTrans.getMxy() , cameraTrans.getMxz() , 0 ,
                                                      cameraTrans.getMyx() , cameraTrans.getMyy() , cameraTrans.getMyz() , 0 ,
                                                      cameraTrans.getMzx() , cameraTrans.getMzy() , cameraTrans.getMzz() , 0 );
            this.getTransforms().clear();
            this.getTransforms().add( billTrans );
            
            // カメラが移動した場合にも
            // 面メッシュがカメラに対して垂直になるように設定
            this.camera.localToSceneTransformProperty().addListener( ( ov , old , current) ->
               {
                    Transform   cameraT     = this.camera.getLocalToSceneTransform();
                    Affine      billT       = new Affine( cameraT.getMxx() , cameraT.getMxy() , cameraT.getMxz() , 0 ,
                                                          cameraT.getMyx() , cameraT.getMyy() , cameraT.getMyz() , 0 ,
                                                          cameraT.getMzx() , cameraT.getMzy() , cameraT.getMzz() , 0 );
                    this.getTransforms().clear();
                    this.getTransforms().add( billT );
                } );
            
        }
        
        /**
         * ビルボードに表示する画像を指定
         * @param img
         */
        public void setImage( Image img )
        {
            // マテリアルに画像を指定
            PhongMaterial   material    = (PhongMaterial) this.getMaterial();
            material.setDiffuseMap( img );
            
            this.setMaterial( material );
        }
        
        /**
         * ビルボードに表示されている画像を取得
         * @return
         */
        public Image getImage()
        {
            // マテリアルから画像を取得
            PhongMaterial   material    = (PhongMaterial) this.getMaterial();
            
            return ( material == null )? null : material.getDiffuseMap();
        }
        
        /**
         * トライアングル・メッシュを作成
         * 
         * 【メッシュ】
         * p0┏━┓p3
         *   ┃\┃
         * p1┗━┛p2
         * 
         * 【テクスチャ】
         * t0┏━┓t3
         *   ┃ ┃
         * t1┗━┛t2
         * 
         * @return
         */
        private Mesh createTriangleMesh()
        {
            // メッシュを作成
            TriangleMesh    mesh        = new TriangleMesh();
            float[]         points      = { -1      ,-1     ,0      ,   // p0
                                            -1      ,1      ,0      ,   // p1
                                            1       ,1      ,0      ,   // p2
                                            1       ,-1     ,0      };  // p3
            float[]         texCoords   = { 0 , 0 ,     // t0
                                            0 , 1 ,     // t1
                                            1 , 1 ,     // t2
                                            1 , 0 };    // t3
            int[]           faces       = { 0 , 0 , 1 , 1 , 2 , 2,
                                            2 , 2 , 3 , 3 , 0 , 0 };
            mesh.getPoints().addAll( points );
            mesh.getTexCoords().addAll( texCoords );
            mesh.getFaces().addAll( faces );
            
            return mesh;
        }
        
    }
}
◇リソース
 (chara_one.png)

◇実行結果


◇解説
 サンプルコードでは、ビルボードを表すクラスをMeshViewを継承して作成している(122行目~230行目)。コンストラクタでは、面メッシュと空のマテリアルを作成し、面をカメラ方向に向けている(134行目~153行目)。さらにカメラの動きに連動して面方向を変えるため、camera.localToSceneTransformProperty(シーンに対するカメラの変換行列プロパティ)に対してバインディングを利用して面方向を変える設定を行っている。

 また、ビルボードに表示する画像についてはα値が適用される。このためサンプルのように透過png等を利用すると、四角形のビルボードであってもα値にあわせてクリッピングされる。

■ 参照

  1. [エフェクト講座] ビルボードって何?
- PR -
Home > Java 応用・実験 > JavaFX ビルボード

- ランダム記事 -

コメント

ただいまコメントを受けつけておりません。

QRコード

プロフィール

管理者:
連絡はContactよりお願いします。

PR