Flex モバイルアプリ上で Stage3D コンテンツを作成するときの留意点

2012.04.12

先日 おおはし より告知させていただいた Air4AndroidSample のアップデートですが、私が担当した箇所で実際に行った留意点を完結にまとめておきます。

AGALMiniAssembler.as の調達

AGALMiniAssembler クラスは、AGAL(Adobe Graphics Assembly Language) アセンブリ言語プログラムを AGAL バイトコードにコンパイルするユーティリティクラスです。
Adobe 公式のクラス (?) なのですが、下記 URL より自前で仕入れなければなりません。
https://github.com/Barliesque/EasyAGAL

アプリケーション記述ファイルの設定値の変更

今回、使用している AIR SDK を 3.2 にアップデートしたことにより、デバイス上でも Stage3D が使用できるようになりました。AppName-app.xml の変更点は以下の 3 点です。

名前空間のバージョン値変更

<application xmlns="http://ns.adobe.com/air/application/3.2">

これはお約束ですね。

描画モードを direct に変更

<renderMode>direct</renderMode>

depthAndStencil タグの値変更

<depthAndStencil>true</depthAndStencil>

今回の AIR 3.2 から追加されたタグですが、デフォルト値が false なので true に修正します。
ちなみに、深度バッファやステンシルバッファを使用しない場合は false のままで問題ありませんが、立体を回転させるような場合は true にしなければなりません。

以上の内容より詳しい情報は、上条さんの記事にまとめられています。

Flex モバイルフレームワーク対応

Flex モバイル AIR プロジェクトのアプリケーションテンプレートが、「ビューベースアプリケーション」まはた「タブ付きアプリケーション」の場合 ( View.as を使用する場合 ) に気を付けるべき点があります。

Stage3D のロジック記述場所

View のコンテンツと Stage3D コンテンツを関連付けさせる場合は、従来通り各 View クラスにロジックを実装していくのが筋と考えます。

Context3D の生成と破棄

Context3D の生成と破棄は、それぞれ ViewNavigatorEvent.VIEW_ACTIVATE、ViewNavigatorEvent.VIEW_DEACTIVATE イベントのタイミングで行えば問題なく View の遷移が可能です。

View の背景を透過にする

Stage3D の描画階層は、表示オブジェクトの下の階層に位置するため、Stage3D の描画内容を表示するときには、表示オブジェクトを不可視状態にする必要があります。

以上の注意点を踏まえた View のサンプルコードは以下のとおりです。

<?xml version="1.0" encoding="utf-8"?>
<!---
<p>Stage3D Sample</p>
@autor taiga
-->
<s:View
xmlns:fx        = "http://ns.adobe.com/mxml/2009"
xmlns:s         = "library://ns.adobe.com/flex/spark"
title           = "Stage3D (2D)"
backgroundAlpha = "0"
>

<fx:Script>
<![CDATA[
    import com.adobe.utils.AGALMiniAssembler;
    import flash.display3D.Context3D;
    import flash.display3D.Context3DProgramType;
    import flash.display3D.Context3DRenderMode;
    import flash.display3D.Context3DVertexBufferFormat;
    import flash.display3D.IndexBuffer3D;
    import flash.display3D.Program3D;
    import flash.display3D.VertexBuffer3D;
    import spark.events.ViewNavigatorEvent;
    protected var r:int;
    protected var context3D:Context3D;
    /** @private */
    protected override function createChildren():void {
        super.createChildren();
        addEventListener(ViewNavigatorEvent.VIEW_ACTIVATE, viewActivateHandler, false, 0, true);
        addEventListener(ViewNavigatorEvent.VIEW_DEACTIVATE, viewDeactivateHandler, false, 0, true);
    }
    /**
     * <p>コンポーネントがアクティブ化されたときのイベントハンドラ</p>
     * @param event ViewNavigatorEvent オブジェクト
     */
    protected function viewActivateHandler(event:ViewNavigatorEvent):void {
        initStage3D();
    }
    /**
     * <p>コンポーネントが非アクティブ化されたときのイベントハンドラ</p>
     * @param event ViewNavigatorEvent オブジェクト
     */
    protected function viewDeactivateHandler(event:ViewNavigatorEvent):void {
        removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
        if(context3D != null) {
            stage.stage3Ds[0].removeEventListener(Event.CONTEXT3D_CREATE, stageContext3dCreateHandler);
            context3D.dispose();
        }
    }
    /**
     * <p>Stage3D の初期化処理</p>
     */
    protected function initStage3D():void {
        var s3d:Stage3D;
        s3d = stage.stage3Ds[0];
        s3d.addEventListener(Event.CONTEXT3D_CREATE, stageContext3dCreateHandler, false, 0, true);
        s3d.requestContext3D(Context3DRenderMode.AUTO);
    }
    /**
     * <p>Context3D オブジェクト作成されたときのイベントハンドラ</p>
     * @param event Event オブジェクト
     */
    protected function stageContext3dCreateHandler(event:Event):void {
        context3DSetting( ( event.target as Stage3D ).context3D );
    }
    /**
     * <p>Context3D オブジェクトの設定</p>
     * @param c3d Context3D オブジェクト
     */
    protected function context3DSetting(c3d:Context3D):void {
        context3D = c3d;
        const VERTEX_SHADER:String =
            "m44 op, va0, vc0 \n" +
            "mov v0, va1";
        
        const FRAGMENT_SHADER:String = "mov oc, v0";
        
        context3D.enableErrorChecking = true;
        context3D.configureBackBuffer(stage.width, stage.height, 2, false);
        
        var vertexData:Vector.<Number> = Vector.<Number>([
            -0.3, -0.3, 0,   1, 0, 0, 1,
            -0.3,  0.3, 0,   0, 1, 0, 1,
            0.3,  0.3, 0,   0, 0, 1, 1,
            0.3, -0.3, 0,   1, 1, 1, 1,
        ]);
        var vertices:VertexBuffer3D;
        vertices = context3D.createVertexBuffer(4,7);
        vertices.uploadFromVector(vertexData, 0, 4);
        context3D.setVertexBufferAt(0, vertices, 0, Context3DVertexBufferFormat.FLOAT_3);
        context3D.setVertexBufferAt(1, vertices, 3, Context3DVertexBufferFormat.FLOAT_4);
        
        var vertexAssembly:AGALMiniAssembler = new AGALMiniAssembler();
        vertexAssembly.assemble(Context3DProgramType.VERTEX, VERTEX_SHADER);
        
        var fragmentAssembly:AGALMiniAssembler = new AGALMiniAssembler();
        fragmentAssembly.assemble(Context3DProgramType.FRAGMENT, FRAGMENT_SHADER);
        
        var programPair:Program3D;
        programPair = context3D.createProgram();
        programPair.upload(vertexAssembly.agalcode, fragmentAssembly.agalcode);
        context3D.setProgram(programPair);
        addEventListener(Event.ENTER_FRAME, enterFrameHandler);
    }
    /**
     * <p>View の EnterFrame イベントハンドラ</p>
     * @param event Event オブジェクト
     */
    protected function enterFrameHandler(event:Event):void {
        render();
    }
    /**
     * <p>Context3D オブジェクトの描画更新処理</p>
     */
    protected function render():void {
        var indexData:Vector.<uint> = Vector.<uint>([
            0, 1, 2,
            2, 3, 0,
        ]);
        var indices:IndexBuffer3D;
        indices = context3D.createIndexBuffer(6);
        indices.uploadFromVector(indexData, 0, 6);
        var matrix3D:Matrix3D = new Matrix3D();
        r++;
        matrix3D.appendTranslation(Math.cos(Math.PI/180 * r), Math.sin(Math.PI/180 * r), 0);
        context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, matrix3D);
        context3D.clear(0, 0, 0);
        context3D.drawTriangles(indices, 0, -1);
        context3D.present();
    }
]]>
</fx:Script>
    
</s:View>