株式会社antsのホームページへようこそ。

[AS3.0]Papervision3Dを試してみた

0
Posted in Lab. By kiyokazk

こんにちは、はじめまして。kiyokazkです。3D関係をメインでやってます。
最近Flashを触る機会が多くなってきたので、3D野郎の端くれとして遅ればせながらPapervision3Dを試してみました。ちなみに今回使用したのはActionScript3.0 + Papervision3D 1.5です。
さて、それじゃあ何を作ろうかなと考えたんですが、最初はやっぱり目玉でしょう!ということでGIZMOにも昔から同梱されている目ん玉グルグルを3Dで表現してみることにします。それでは順番に内容を見ていきましょう。


■Papervision3Dを準備する
Papervision3D 1.5を持っていない人はまずは手に入れる必要があります。
基本的にはSubversionで入手しますが(リポジトリは http://svn1.cvsdude.com/osflash/papervision3d )、Subversionを使わなくてもバージョン1.5はzip圧縮したものがコチラからダウンロードすることができます。
適当なディレクトリにダウンロードしたらFlashでクラスパスを通しておきましょう。
Subversionでゲットした人はas3/trunk/src/に、1.5のパッケージをダウンロードした人はsrc/に通します。これで準備完了です。

■ガシガシ作る
さてそれでは作っていきましょう。
まずは適当にドキュメントクラスを作ります。


package {
  import flash.display.Sprite;
  import flash.events.Event;
  public class Main extends Sprite {
    //コンストラクタ
    public function Main():void {
      //3Dの初期化
      init3D();
      //レンダリングループの登録
      this.addEventListener( Event.ENTER_FRAME,  enterFrameHandler );
    }
  }
}

大枠ができたらあとは3Dの初期化部分(init3D)とレンダリングループ(enterFrameHandler)を作っていきます。
まずは3Dの初期化部分から作りましょう。モデルデータのロード等もここで行います。それでは順に見ていきます。シーンの土台となるコンテナを作成します。ステージの中心を原点にしましょう。


      //コンテナを作成
      _container = new Sprite();
      _container.x = stage.stageWidth / 2;
      _container.y = stage.stageHeight / 2;
      addChild( _container );

シーンを作成します。


      //シーンを作成
      _scene = new Scene3D( _container );

カメラを作成します。


      //カメラを作成
      _camera = new Camera3D();
      _camera.y = 100;
      _camera.z = -300;

白目部分と黒目部分のマテリアルを作成します。
白目はグレーの半透明、黒目は黒のColorMaterialを使います。注意事項としては、Papervision3DのドキュメントではColorMaterialのコンストラクタのalphaのデフォルトは100と書かれているんですが、中身をみると実はデフォルトは1が正しいです。なので半透明にするために白目は0.5を第二引数に渡します。
Colladaのロード時に渡すマテリアルリストもここで作っておきます。


      //マテリアルを作成
      _materialEyes = new ColorMaterial( 0x808080, 0.5 );
      _materialBall = new ColorMaterial( 0x000000 );
      //マテリアルリストを作成
      _materialsListEyes = new MaterialsList();
      _materialsListEyes.addMaterial( _materialEyes, "initialShadingGroup" );

白目部分のモデルを読み込みます。
今回はColladaの読み込みも試してみたかったので、白目部分をMayaで作成してColladaファイルとしてエクスポートしたものを使っています。エクスポーターとしてFeeling SoftwareのColladaMayaエクスポータを使用して、エクスポートオプションはPaparvision3Dのドキュメントにあるとおりの設定(General Export OptionsはRelative PathsとTriangulateにチェック、Filter ExportはPolygon meshes、Normals、Texture Coordinatesにチェック)でエクスポートしています。
ロードする際には先ほど作成したマテリアルリストを指定します。


      //Colladaのロード
      _eyes = new Collada( "eyes.dae", _materialsListEyes );
      _scene.addChild( _eyes );

黒目部分を作成します。
こちらは白目部分とは違ってPapervision3Dが最初から持っているプリミティブオブジェクトのSphereを使うことにします。


      //球の作成
      _ballL = new Sphere( _materialBall, BALL_SIZE );
      _ballL.x = -BALL_POS_X;
      _ballR = new Sphere( _materialBall, BALL_SIZE );
      _ballR.x = BALL_POS_X
      _scene.addChild( _ballL );
      _scene.addChild( _ballR );

これで初期化部分はおしまいです。
次にレンダリングループを書けば完成です。順に見ていきましょう。
3Dっぽくするために全体をぐるぐると回転させておくことにします。


      //回転させる
      _eyes.yaw( 5 );

黒目部分の位置をマウスの位置に応じて移動するように決めます。
本来であればZの位置も計算しようと思ったんですが、ワールド座標⇔スクリーン座標のそれぞれの変換が簡単にできる便利そうな関数がPapervision3Dには用意されてないっぽかったので、今回は手抜きでXY座標だけの移動でそれっぽく見えるようにしてみました。
ちゃんとやるにはスクリーン座標のマウス位置からワールドの位置を適当に決めて、黒目がその点に向かうようにするべきでしょう。


      //黒目位置の計算
      var centerX:Number = BALL_POS_X * Math.cos( _eyes.rotationY * Math.PI / 180 );
      var centerZ:Number = BALL_POS_X * Math.sin( _eyes.rotationY * Math.PI / 180 );
      _ballL.x = -centerX + _container.mouseX * BALL_MOVE_LEN / _container.x;
      _ballL.y = -( _container.mouseY * BALL_MOVE_LEN / _container.y );
      _ballL.z = -centerZ;
      _ballR.x = centerX + _container.mouseX * BALL_MOVE_LEN / _container.x;
      _ballR.y = -( _container.mouseY * BALL_MOVE_LEN / _container.y );
      _ballR.z = centerZ;

最後にシーンをレンダリングします。


      //シーンのレンダリング
      _scene.renderCamera( _camera );

これで完了です。
できたものは↓(クリックすると開きます)。
・・・がPapervision3D 1.5にはそもそもライトがないのでシェーディングはされないし、テクスチャも貼っていないのでのっぺりと見えますね。次はライトが使えるようになったPapervision3D 2.0を試してみようかな。
eyeball_image.jpg
あと、実は半透明の描画順が正しくないです、というか描画順をいじることはできない気がします。Papervision3DはZバッファによる陰面消去ではなくてZソートを使っているのであまり違和感なく見えるんですね。最後に全部のソースを貼っておきます。


package {
  import flash.display.Sprite;
  import flash.events.Event;
  import org.papervision3d.cameras.Camera3D;
  import org.papervision3d.scenes.Scene3D;
  import org.papervision3d.objects.DisplayObject3D;
  import org.papervision3d.objects.Collada;
  import org.papervision3d.objects.Sphere;
  import org.papervision3d.materials.*;
  
  public class Main extends Sprite {
    private var _container:Sprite;
    private var _scene:Scene3D;
    private var _camera:Camera3D;
    private var _eyes:Collada;
    private var _ballL:Sphere;
    private var _ballR:Sphere;
    private var _materialsListEyes:MaterialsList;
    private var _materialEyes:ColorMaterial;
    private var _materialBall:ColorMaterial;
    private static const BALL_SIZE:Number = 20;
    private static const BALL_POS_X:Number = 100;
    private static const BALL_MOVE_LEN:Number = 70;
    
    //コンストラクタ
    public function Main():void {
      //3Dの初期化
      init3D();
      //レンダリングループの登録
      this.addEventListener( Event.ENTER_FRAME,  enterFrameHandler );
    }
    
    //3Dの初期化
    private function init3D():void {
      //コンテナを作成
      _container = new Sprite();
      _container.x = stage.stageWidth / 2;
      _container.y = stage.stageHeight / 2;
      addChild( _container );
      
      //シーンを作成
      _scene = new Scene3D( _container );
      
      //カメラを作成
      _camera = new Camera3D();
      _camera.y = 100;
      _camera.z = -300;
      
      //マテリアルを作成l
      _materialEyes = new ColorMaterial( 0x808080, 0.5 );
      _materialBall = new ColorMaterial( 0x000000 );
      
      //マテリアルリストを作成
      _materialsListEyes = new MaterialsList();
      _materialsListEyes.addMaterial( _materialEyes, "initialShadingGroup" );
      
      //Colladaの読み込み
      _eyes = new Collada( "eyes.dae", _materialsListEyes );
      _scene.addChild( _eyes );
      
      //球の作成
      _ballL = new Sphere( _materialBall, BALL_SIZE );
      _ballL.x = -BALL_POS_X;
      _ballR = new Sphere( _materialBall, BALL_SIZE );
      _ballR.x = BALL_POS_X
      _scene.addChild( _ballL );
      _scene.addChild( _ballR );
      
    }
    
    //レンダリングループ
    private function enterFrameHandler( event:Event ):void {
      //回転させる
      _eyes.yaw( 5 );
      
      //黒目位置の計算
      var centerX:Number = BALL_POS_X * Math.cos( _eyes.rotationY * Math.PI / 180 );
      var centerZ:Number = BALL_POS_X * Math.sin( _eyes.rotationY * Math.PI / 180 );
      _ballL.x = -centerX + _container.mouseX * BALL_MOVE_LEN / _container.x;
      _ballL.y = -( _container.mouseY * BALL_MOVE_LEN / _container.y );
      _ballL.z = -centerZ;
      _ballR.x = centerX + _container.mouseX * BALL_MOVE_LEN / _container.x;
      _ballR.y = -( _container.mouseY * BALL_MOVE_LEN / _container.y );
      _ballR.z = centerZ;
      
      //シーンをレンダリングする
      _scene.renderCamera( _camera );
    }
  }
}