書き物

技術とか作った物の話とか愚痴文句感想など

Blender で作ったものを Flash で表示する(マテリアルとかテクスチャとかも)

Blender で作ったものを Flash で表示する の続きです。

前回はマテリアルもテクスチャもライトも何も使わなかったので、真っ白な恐らく 3D っぽい何かが表示されていました。マテリアルとかテクスチャ含め、Blender で作ったものを Away3DFlash 表示させることが出来たので、記録しておきます。

対象環境

1. UV レイアウト出力

前回作ったものに、マテリアル、テクスチャを追加します。Blender の操作なので説明は割愛。 テクスチャを作るのに、思いの外 Flash が活躍したのでこれだけ書いておきます。

今回は立体のエッジに対して線を入れたかったので、各面を囲うようなテクスチャを作ります。 以下の立体を エディットモード 以下のように UV 展開します。あとで Flash に読み込ませるので、線は極力真っ直ぐにしておいたほうが良いと思います。 UVレイアウト これを「Export UV Layout」で png ファイル出力します。サイズは 1024 × 1024。 ↓みたいなファイルが出力されます。(こんなシンプルな画像ながらサイズが 4M 超え!) UVレイアウト出力結果 これをそのままテクスチャとして使うと、最初の立体の画像のまんまに線が入ってしまうのでよろしくないです。斜めの線とかいらないです。

2. UV レイアウト修正

出力した UV レイアウトの png ファイルを編集します。いらない線を消すとか、画像編集ソフトの扱いに慣れていない僕には地獄の作業ですが、Flash でやってみたら簡単にやりたいことが出来ました。

まずは Flash で新規作成、ステージサイズを 1024 × 1024 にしてから UV レイアウトの png ファイルを読み込ませて配置します。 Flash のステージに UV レイアウト配置 配置したビットマップを選択した状態で「修正 > ビットマップ > ビットマップのトレース」を実行します。設定は精度 MAX で。 ビットマップのトレース 今回のようなシンプルなビットマップなら綺麗にベクター化されるようです。あとは色を変えるなり絵を描くなり、不要な線を消すなりします。修正が終わったら「ファイル > 書き出し > イメージの書き出し」で png ファイルを出力します。

↓修正後 UV レイアウト(サイズが 7K になりました!) 修正後 UV レイアウト

3. メッシュデータ作成

Blender で、上記で作った UV レイアウトの画像ファイルをテクスチャとして適用します。

あとは Away3D で読み込ませるためにデータをエクスポートします。今回は「3D Studio (.3ds)」にします。 エクスポート

今回出来たファイルです。 blockParts.blend blockParts.3ds blockParts.png

4. メッシュデータの表示

前回はメッシュデータを外部ファイルとして使用しましたが、本家サンプルに Embed で埋め込んでパースする方法があったので、これを使ってみます。 src/Basic_Load3DS.as at master from away3d/away3d-examples-fp11 - GitHub

今回作ったファイル blockParts.3ds blockParts.png を Embed で swf に埋め込みます。 あと、地面が欲しかったので、Away3D のサンプルから拝借しました。 CoarseRedSand.jpg これも Embed で埋め込みます。

package
{ import away3d.cameras.Camera3D; import away3d.containers.Scene3D; import away3d.containers.View3D; import away3d.controllers.HoverController; import away3d.entities.Mesh; import away3d.events.AssetEvent; import away3d.library.assets.AssetType; import away3d.lights.DirectionalLight; import away3d.loaders.Loader3D; import away3d.loaders.misc.AssetLoaderContext; import away3d.loaders.parsers.Parsers; import away3d.materials.BitmapMaterial; import away3d.materials.methods.FilteredShadowMapMethod; import away3d.primitives.Plane; import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.events.MouseEvent; import flash.geom.Vector3D; import flash.utils.getTimer;

/**
 * Away3D テスト 2
 * 
 * @author asahiufo@AM902
 */
public class Away3DTest2 extends Sprite 
{
    // ブロック
    [Embed(source="/../embeds/models/blockParts.3ds",mimeType="application/octet-stream")]
    private var BlockPartsModel:Class;
    [Embed(source = "/../embeds/textures/blockParts.png")]
    private var BlockPartsTexture:Class;

    // 地面
    [Embed(source="/../embeds/textures/CoarseRedSand.jpg")]
    private var SandTexture:Class;

    // エンジン
    private var _scene:Scene3D;
    private var _camera:Camera3D;
    private var _view:View3D;
    private var _cameraController:HoverController;

    // オブジェクト
    private var _light:DirectionalLight;
    private var _direction:Vector3D;
    private var _loader:Loader3D;
    private var _ground:Plane;

    // マテリアル
    private var _groundMaterial:BitmapMaterial;

    // 制御用
    private var _move:Boolean;
    private var _lastPanAngle:Number;
    private var _lastTiltAngle:Number;
    private var _lastMouseX:Number;
    private var _lastMouseY:Number;

    /**
     * コンストラクタ
     */
    public function Away3DTest2() 
    {
        super();

        addEventListener(Event.ADDED_TO_STAGE, _onAddedToStage);
    }

    /**
     * ステージ追加時イベントハンドラ
     * 
     * @param event イベント
     */
    private function _onAddedToStage(event:Event):void
    {
        stage.scaleMode = StageScaleMode.NO_SCALE;
        stage.align     = StageAlign.TOP_LEFT;

        _move          = false;
        _lastPanAngle  = 0;
        _lastTiltAngle = 0;
        _lastMouseX    = 0;
        _lastMouseY    = 0;

        // =========
        // エンジン
        // =========
        // シーン
        _scene = new Scene3D();

        // カメラ
        _camera = new Camera3D();
        _camera.lens.far = 2100;

        // ビュー
        _view = new View3D();
        _view.scene  = _scene;
        _view.camera = _camera;
        addChild(_view);

        // カメラコントローラー
        _cameraController = new HoverController(_camera, null, 45, 20, 1000, 10);

        // =============
        // オブジェクト
        // =============
        Parsers.enableAllBundled();

        // ライト
        _light = new DirectionalLight(-1, -1, 1);
        _direction = new Vector3D(-1, -1, 1);
        _scene.addChild(_light);

        // ブロック
        var assetLoaderContext:AssetLoaderContext = new AssetLoaderContext();
        assetLoaderContext.mapUrlToData("blockParts.p", new BlockPartsTexture());

        _loader = new Loader3D();
        _loader.scale(100);
        _loader.y = 200;
        _loader.z = -200;
        _loader.addEventListener(AssetEvent.ASSET_COMPLETE, _onAssetComplete);
        _loader.loadData(new BlockPartsModel(), assetLoaderContext);
        _scene.addChild(_loader);


        // 地面
        _groundMaterial = new BitmapMaterial((new SandTexture()).bitmapData);
        _groundMaterial.lights = [_light];
        _groundMaterial.specular = 0;
        _groundMaterial.shadowMethod = new FilteredShadowMapMethod(_light);
        _ground = new Plane(_groundMaterial, 1000, 1000);
        _scene.addChild(_ground);

        // リスナー
        addEventListener(Event.ENTER_FRAME, _onEnterFrame);
        stage.addEventListener(MouseEvent.MOUSE_DOWN, _onMouseDown);
        stage.addEventListener(MouseEvent.MOUSE_UP, _onMouseUp);
        stage.addEventListener(Event.RESIZE, _onResize);
        _onResize();
    }

    /**
     * エンターフレームイベントハンドラ
     * 
     * @param event イベント
     */
    private function _onEnterFrame(event:Event):void
    {
        if (_move)
        {
            _cameraController.panAngle  = 0.3 * (stage.mouseX - _lastMouseX) + _lastPanAngle;
            _cameraController.tiltAngle = 0.3 * (stage.mouseY - _lastMouseY) + _lastTiltAngle;
        }

        _direction.x = -Math.sin(getTimer() / 4000);
        _direction.z = -Math.cos(getTimer() / 4000);
        _light.direction = _direction;

        _view.render();
    }

    /**
     * モデルデータパース完了イベントハンドラ
     * 
     * @param event イベント
     */
    private function _onAssetComplete(event:AssetEvent):void
    {
        if (event.asset.assetType == AssetType.BITMAP)
        {
        }
        else if (event.asset.assetType == AssetType.MATERIAL)
        {
            var material:BitmapMaterial = event.asset as BitmapMaterial;
            material.shadowMethod = new FilteredShadowMapMethod(_light);
            material.lights       = [_light];
            material.gloss        = 30;
            material.specular     = 1;
            material.ambientColor = 0x303040;
            material.ambient      = 1;
        }
        else if (event.asset.assetType == AssetType.GEOMETRY)
        {
        }
        else if (event.asset.assetType == AssetType.MESH)
        {
            var mesh:Mesh = event.asset as Mesh;
            mesh.castsShadows = true;
        }
    }

    /**
     * マウスダウンイベントハンドラ
     * 
     * @param event イベント
     */
    private function _onMouseDown(event:MouseEvent):void
    {
        _lastPanAngle  = _cameraController.panAngle;
        _lastTiltAngle = _cameraController.tiltAngle;
        _lastMouseX    = stage.mouseX;
        _lastMouseY    = stage.mouseY;
        _move          = true;

        stage.addEventListener(Event.MOUSE_LEAVE, _onStageMouseLeave);
    }

    /**
     * マウスアップイベントハンドラ
     * 
     * @param event イベント
     */
    private function _onMouseUp(event:MouseEvent):void
    {
        _move = false;
        stage.removeEventListener(Event.MOUSE_LEAVE, _onStageMouseLeave);
    }

    /**
     * マウス外しイベントハンドラ
     * 
     * @param event イベント
     */
    private function _onStageMouseLeave(event:Event):void
    {
        _move = false;
        stage.removeEventListener(Event.MOUSE_LEAVE, _onStageMouseLeave);
    }

    /**
     * ステージリサイズイベントハンドラ
     * 
     * @param event イベント
     */
    private function _onResize(event:Event = null):void
    {
        _view.width = stage.stageWidth;
        _view.height = stage.stageHeight;
    }
}

} 実行結果 実行結果

Blender で作ったメッシュデータを Away3D で使う場合のコードの要点は以下でしょうか。

  • ロードの起点はメッシュデータ(*.obj, *.3dsなど)。Loader3D を使用。
  • Away3D のパーサはメッシュデータを元に他の素材もまとめてロード or パースする。AssetLoaderContext で関連データを定義して Loader3D に巻き込む。
  • AssetEvent.ASSET_COMPLETE イベントは、データを読み込む度に呼び出される模様。今回のデータの場合、AssetType.BITMAP、AssetType.MATERIAL、AssetType.GEOMETRY、AssetType.MESH の 4 種類分合計 4 回呼び出されました。これをどのように使うかというとよく分かりません。
  • assetLoaderContext.mapUrlToData()に渡す、素材の識別子って何で決まるのか分かりません。Blender ではテクスチャ設定として "blockParts.png" を指定しましたが、なぜか Away3D は "blockParts.p" を要求してきました。("blockParts.p" が見つからないという例外メッセージで判明)

終わりに

とりあえずここまで来れば、あとは物体を Away3D でグリグリ動かせるようになればゲームが作れそうな気がします。